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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,14 @@
1
+ import { debugLog } from './constants';
2
+ // Module load marker - unique ID for tracking code version
3
+ // DEBUG FLAG: Set to false to disable all console logs for better performance
4
+ const POPUP_DEBUG = false;
5
+ const debugLog = (...args) => {
6
+ if (POPUP_DEBUG) {
7
+ debugLog(...args);
8
+ }
9
+ };
10
+ // Only log module load in debug mode
11
+ debugLog('POPUP_MODULE_V17_LOADED at ' + new Date().toISOString());
1
12
  import React, { forwardRef, memo, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState, } from 'react';
2
13
  import { Dimensions, findNodeHandle, Keyboard, Platform, StatusBar, Text, TextInput as RNTextInput, TouchableOpacity, View, } from 'react-native';
3
14
  import { AdvancedFlatList } from 'react-native-advanced-flatlist';
@@ -6,7 +17,7 @@ import { useRootView } from './RootViewContext';
6
17
  import { useKeyboardStatus } from './KeyboardManager';
7
18
  const queryChangeListeners = [];
8
19
  const emitQueryChange = (query) => {
9
- console.log('AutoPositionedPopup.tsx emitQueryChange query=', query, ' listeners=', queryChangeListeners.length);
20
+ debugLog('AutoPositionedPopup.tsx emitQueryChange query=', query, ' listeners=', queryChangeListeners.length);
10
21
  queryChangeListeners.forEach((l) => l(query));
11
22
  };
12
23
  const subscribeQueryChange = (listener) => {
@@ -34,14 +45,14 @@ const ListItem = memo(({ updateState, item, index, selectedItem, themeMode }) =>
34
45
  rootViewsRef.current = rootViews;
35
46
  }, [rootViews]);
36
47
  return useMemo(() => {
37
- // console.log('AutoPositionedPopup.tsx ListItem=', {index, item, selectedItem});
48
+ // debugLog('AutoPositionedPopup.tsx ListItem=', {index, item, selectedItem});
38
49
  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
50
  return (<TouchableOpacity key={item.id} style={[
40
51
  styles.commonModalRow,
41
52
  { backgroundColor: isSelected ? (themeMode === 'light' ? 'rgba(116, 116, 128, 0.08)' : 'rgba(120, 120, 128, 0.36)') : 'transparent' },
42
53
  ]} onPress={() => {
43
- // console.log('AutoPositionedPopup.tsx ListItem onPress item=', item); // Commented to prevent spam
44
- // console.log('AutoPositionedPopup.tsx ListItem onPress rootViews=', rootViewsRef.current); // Commented to prevent spam
54
+ // debugLog('AutoPositionedPopup.tsx ListItem onPress item=', item); // Commented to prevent spam
55
+ // debugLog('AutoPositionedPopup.tsx ListItem onPress rootViews=', rootViewsRef.current); // Commented to prevent spam
45
56
  updateState('selectedItem', item);
46
57
  }}>
47
58
  <Text style={(themeMode === 'light' ? styles.ListItemCode : Object.assign(Object.assign({}, styles.ListItemCode), { color: '#fff' }))} numberOfLines={1} ellipsizeMode="tail">
@@ -69,16 +80,16 @@ const AutoPositionedPopupList = memo(({ tag, updateState, fetchData, keyExtracto
69
80
  useEffect(() => {
70
81
  (async () => {
71
82
  })();
72
- console.log(`AutoPositionedPopupList componentDidMount`);
83
+ debugLog(`AutoPositionedPopupList componentDidMount`);
73
84
  //componentWillUnmount
74
85
  return () => {
75
- console.log(`AutoPositionedPopupList componentWillUnmount`);
86
+ debugLog(`AutoPositionedPopupList componentWillUnmount`);
76
87
  setSearchQuery('');
77
88
  };
78
89
  }, []);
79
90
  useEffect(() => {
80
91
  const unsubscribe = subscribeQueryChange((newQuery) => {
81
- console.log('AutoPositionedPopupList useEffect subscribeQueryChange newQuery=', newQuery);
92
+ debugLog('AutoPositionedPopupList useEffect subscribeQueryChange newQuery=', newQuery);
82
93
  ref_searchQuery.current = newQuery;
83
94
  if (ref_list.current) {
84
95
  ref_list.current.scrollToTop();
@@ -88,19 +99,19 @@ const AutoPositionedPopupList = memo(({ tag, updateState, fetchData, keyExtracto
88
99
  return unsubscribe;
89
100
  }, []);
90
101
  const _updateState = (key, value) => {
91
- console.log('AutoPositionedPopupList _updateState key=', key, ' value=', value);
102
+ debugLog('AutoPositionedPopupList _updateState key=', key, ' value=', value);
92
103
  setState((prevState) => (Object.assign(Object.assign({}, prevState), { [key]: value })));
93
- console.log('AutoPositionedPopupList _updateState rootViews=', rootViewsRef.current);
104
+ debugLog('AutoPositionedPopupList _updateState rootViews=', rootViewsRef.current);
94
105
  updateState(key, value);
95
106
  };
96
107
  const _fetchData = async ({ pageIndex, pageSize: currentPageSize, }) => {
97
- console.log('AutoPositionedPopupList _fetchData=', { pageIndex, pageSize: currentPageSize, 'state.localData': state.localData, 'ref_searchQuery.current': ref_searchQuery.current, localSearch });
108
+ debugLog('AutoPositionedPopupList _fetchData=', { pageIndex, pageSize: currentPageSize, 'state.localData': state.localData, 'ref_searchQuery.current': ref_searchQuery.current, localSearch });
98
109
  if (localSearch && state.localData.length > 0) {
99
110
  const result = state.localData.filter((item) => {
100
111
  var _a;
101
112
  return (_a = `${item.title}`) === null || _a === void 0 ? void 0 : _a.toLowerCase().includes(ref_searchQuery.current.toLowerCase());
102
113
  });
103
- console.log('AutoPositionedPopupList _fetchData localSearch result=', result);
114
+ debugLog('AutoPositionedPopupList _fetchData localSearch result=', result);
104
115
  return Promise.resolve({
105
116
  items: result,
106
117
  pageIndex: 0,
@@ -113,7 +124,7 @@ const AutoPositionedPopupList = memo(({ tag, updateState, fetchData, keyExtracto
113
124
  pageSize: pageSize || 10,
114
125
  searchQuery: ref_searchQuery.current,
115
126
  });
116
- console.log('AutoPositionedPopupList _fetchData res=', res);
127
+ debugLog('AutoPositionedPopupList _fetchData res=', res);
117
128
  if ((res === null || res === void 0 ? void 0 : res.items) && localSearch) {
118
129
  setState((prevState) => {
119
130
  return Object.assign(Object.assign({}, prevState), { localData: res.items });
@@ -130,16 +141,16 @@ const AutoPositionedPopupList = memo(({ tag, updateState, fetchData, keyExtracto
130
141
  return null;
131
142
  }
132
143
  catch (e) {
133
- console.warn('Error in fetchData:', e);
144
+ debugLog('Error in fetchData:', e);
134
145
  }
135
- console.log('AutoPositionedPopupList _fetchData res=', null);
146
+ debugLog('AutoPositionedPopupList _fetchData res=', null);
136
147
  return null;
137
148
  };
138
149
  const _renderItem = useCallback(({ item, index }) => {
139
150
  return <ListItem item={item} index={index} updateState={_updateState} selectedItem={state.selectedItem} themeMode={themeMode}/>;
140
151
  }, [state.selectedItem, themeMode]);
141
152
  return useMemo(() => {
142
- console.log('AutoPositionedPopupList (global as any)?.$fake=', global === null || global === void 0 ? void 0 : global.$fake);
153
+ debugLog('AutoPositionedPopupList (global as any)?.$fake=', global === null || global === void 0 ? void 0 : global.$fake);
143
154
  // Babel configuration handles the path redirection based on global.$fake
144
155
  // No need for conditional import here
145
156
  return (<View style={[styles.baseModalView, styles.autoPositionedPopupList, { backgroundColor: themeMode === 'light' ? '#fff' : 'rgba(44, 44, 46, 1)', }]}>
@@ -164,7 +175,7 @@ const listLayout = {
164
175
  };
165
176
  // Main AutoPositionedPopup component
166
177
  const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
167
- console.log('AutoPositionedPopup props=', props);
178
+ debugLog('AutoPositionedPopup props=', props);
168
179
  const { tag, style, AutoPositionedPopupBtnStyle, placeholder = 'Please Select', onSubmitEditing, TextInputProps, //= {autoFocus: true},
169
180
  inputStyle, labelStyle, popUpViewStyle = { left: '5%', width: '90%' }, fetchData = async ({ pageIndex, pageSize, searchQuery, }) => {
170
181
  const res = {
@@ -174,12 +185,12 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
174
185
  };
175
186
  try {
176
187
  // const res1: any[] = await $api.xxx(pageSize)
177
- // console.log('${NAME} xxx res=', res)
188
+ // debugLog('${NAME} xxx res=', res)
178
189
  // res.items = res1
179
190
  // res.needLoadMore = res1.length === pageSize
180
191
  }
181
192
  catch (e) {
182
- console.warn('Error in fetch operation:', e);
193
+ debugLog('Error in fetch operation:', e);
183
194
  }
184
195
  return res;
185
196
  }, renderItem, onItemSelected, localSearch = false, pageSize = 20, selectedItem, useTextInput = false, btwChildren, CustomRow = ({ children }) => <View>{children}</View>, keyExtractor = (item) => String((item === null || item === void 0 ? void 0 : item.id) || ''), AutoPositionedPopupBtnDisabled = false, forceRemoveAllRootViewOnItemSelected = false, centerDisplay = false, selectedItemBackgroundColor = 'rgba(116, 116, 128, 0.08)',
@@ -191,7 +202,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
191
202
  selectedItem: selectedItem,
192
203
  });
193
204
  // Use RootView context
194
- const { addRootView, setRootViewNativeStyle, removeRootView, rootViews, setSearchQuery } = useRootView();
205
+ const { addRootView, setRootViewNativeStyle, updateRootView, removeRootView, rootViews, setSearchQuery } = useRootView();
195
206
  const rootViewsRef = useRef(rootViews);
196
207
  // Track TextInput focus and RootView states like project implementation
197
208
  const hasTriggeredFocus = useRef(false);
@@ -244,7 +255,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
244
255
  */
245
256
  const scrollParentToTrigger = useCallback(() => {
246
257
  if (!(parentScrollViewRef === null || parentScrollViewRef === void 0 ? void 0 : parentScrollViewRef.current) || !triggerBtnRef.current) {
247
- console.log('AutoPositionedPopup scrollParentToTrigger: No parentScrollViewRef or triggerBtnRef available');
258
+ debugLog('AutoPositionedPopup scrollParentToTrigger: No parentScrollViewRef or triggerBtnRef available');
248
259
  return;
249
260
  }
250
261
  // Use scrollToFocusedInput method from KeyboardAwareScrollView
@@ -252,16 +263,16 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
252
263
  const scrollView = parentScrollViewRef.current;
253
264
  const nodeHandle = findNodeHandle(triggerBtnRef.current);
254
265
  if (nodeHandle && scrollView) {
255
- console.log('AutoPositionedPopup scrollParentToTrigger: Scrolling to trigger button with extraHeight=', scrollExtraHeight);
266
+ debugLog('AutoPositionedPopup scrollParentToTrigger: Scrolling to trigger button with extraHeight=', scrollExtraHeight);
256
267
  // KeyboardAwareScrollView has a scrollToFocusedInput method that handles this
257
268
  // However, it requires a ReactNode. We'll use scrollToPosition as an alternative.
258
269
  // First, measure the trigger button position relative to the ScrollView
259
270
  triggerBtnRef.current.measureInWindow((x, y, width, height) => {
260
271
  if (y === undefined || height === undefined) {
261
- console.log('AutoPositionedPopup scrollParentToTrigger: measureInWindow returned undefined');
272
+ debugLog('AutoPositionedPopup scrollParentToTrigger: measureInWindow returned undefined');
262
273
  return;
263
274
  }
264
- console.log('AutoPositionedPopup scrollParentToTrigger: trigger position=', { x, y, width, height });
275
+ debugLog('AutoPositionedPopup scrollParentToTrigger: trigger position=', { x, y, width, height });
265
276
  // Get keyboard height from Keyboard API
266
277
  // On keyboard show, scroll to position that keeps trigger above keyboard
267
278
  Keyboard.addListener('keyboardDidShow', (event) => {
@@ -270,7 +281,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
270
281
  // Calculate if trigger is below keyboard
271
282
  const triggerBottom = y + height;
272
283
  const visibleAreaBottom = screenHeight - keyboardHeight;
273
- console.log('AutoPositionedPopup scrollParentToTrigger: keyboard data=', {
284
+ debugLog('AutoPositionedPopup scrollParentToTrigger: keyboard data=', {
274
285
  keyboardHeight,
275
286
  screenHeight,
276
287
  triggerBottom,
@@ -280,7 +291,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
280
291
  if (triggerBottom > visibleAreaBottom) {
281
292
  // Calculate how much to scroll
282
293
  const scrollAmount = triggerBottom - visibleAreaBottom + scrollExtraHeight;
283
- console.log('AutoPositionedPopup scrollParentToTrigger: scrolling by', scrollAmount);
294
+ debugLog('AutoPositionedPopup scrollParentToTrigger: scrolling by', scrollAmount);
284
295
  // Use scrollForExtraHeightOnAndroid or scrollToPosition
285
296
  if (typeof scrollView.scrollToPosition === 'function') {
286
297
  // scrollToPosition(x, y, animated)
@@ -288,7 +299,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
288
299
  }
289
300
  else if (typeof scrollView.scrollToEnd === 'function') {
290
301
  // Fallback: scroll to end might help in some cases
291
- console.log('AutoPositionedPopup scrollParentToTrigger: using scrollToEnd fallback');
302
+ debugLog('AutoPositionedPopup scrollParentToTrigger: using scrollToEnd fallback');
292
303
  }
293
304
  }
294
305
  });
@@ -300,19 +311,19 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
300
311
  * Uses stored trigger position (captured before TextInput replaces the trigger button)
301
312
  */
302
313
  const scrollToTriggerWithMeasure = useCallback(() => {
303
- console.log('AutoPositionedPopup scrollToTriggerWithMeasure called, tag=', tag, {
314
+ debugLog('AutoPositionedPopup scrollToTriggerWithMeasure called, tag=', tag, {
304
315
  hasParentScrollViewRef: !!(parentScrollViewRef === null || parentScrollViewRef === void 0 ? void 0 : parentScrollViewRef.current),
305
316
  hasTriggerPosition: !!triggerPositionRef.current,
306
317
  triggerPosition: triggerPositionRef.current
307
318
  });
308
319
  if (!(parentScrollViewRef === null || parentScrollViewRef === void 0 ? void 0 : parentScrollViewRef.current)) {
309
- console.log('AutoPositionedPopup scrollToTriggerWithMeasure: parentScrollViewRef not available, tag=', tag);
320
+ debugLog('AutoPositionedPopup scrollToTriggerWithMeasure: parentScrollViewRef not available, tag=', tag);
310
321
  return;
311
322
  }
312
323
  // Use stored trigger position (captured when trigger was clicked)
313
324
  const storedPosition = triggerPositionRef.current;
314
325
  if (!storedPosition) {
315
- console.log('AutoPositionedPopup scrollToTriggerWithMeasure: no stored trigger position, tag=', tag);
326
+ debugLog('AutoPositionedPopup scrollToTriggerWithMeasure: no stored trigger position, tag=', tag);
316
327
  return;
317
328
  }
318
329
  const scrollView = parentScrollViewRef.current;
@@ -324,7 +335,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
324
335
  const keyboardApproxHeight = screenHeight * 0.4; // Conservative estimate
325
336
  const visibleAreaBottom = screenHeight - keyboardApproxHeight;
326
337
  const triggerBottom = triggerY + triggerHeight;
327
- console.log('AutoPositionedPopup scrollToTriggerWithMeasure: calculations=', {
338
+ debugLog('AutoPositionedPopup scrollToTriggerWithMeasure: calculations=', {
328
339
  tag,
329
340
  triggerY,
330
341
  triggerHeight,
@@ -337,7 +348,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
337
348
  if (triggerBottom > visibleAreaBottom) {
338
349
  // Calculate scroll amount to bring trigger above keyboard
339
350
  const scrollAmount = triggerBottom - visibleAreaBottom + scrollExtraHeight;
340
- console.log('AutoPositionedPopup scrollToTriggerWithMeasure: scrolling, amount=', scrollAmount, 'tag=', tag);
351
+ debugLog('AutoPositionedPopup scrollToTriggerWithMeasure: scrolling, amount=', scrollAmount, 'tag=', tag);
341
352
  // Use scrollForExtraHeightOnAndroid for KeyboardAwareScrollView
342
353
  if (typeof scrollView.scrollForExtraHeightOnAndroid === 'function') {
343
354
  scrollView.scrollForExtraHeightOnAndroid(scrollAmount);
@@ -350,11 +361,11 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
350
361
  scrollView.scrollTo({ y: scrollAmount, animated: true });
351
362
  }
352
363
  else {
353
- console.log('AutoPositionedPopup scrollToTriggerWithMeasure: no scroll method available on scrollView');
364
+ debugLog('AutoPositionedPopup scrollToTriggerWithMeasure: no scroll method available on scrollView');
354
365
  }
355
366
  }
356
367
  else {
357
- console.log('AutoPositionedPopup scrollToTriggerWithMeasure: trigger already visible, no scroll needed, tag=', tag);
368
+ debugLog('AutoPositionedPopup scrollToTriggerWithMeasure: trigger already visible, no scroll needed, tag=', tag);
358
369
  }
359
370
  }, [parentScrollViewRef, scrollExtraHeight, tag]);
360
371
  /**
@@ -363,10 +374,10 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
363
374
  useEffect(() => {
364
375
  (async () => {
365
376
  })();
366
- console.log(`AutoPositionedPopup componentDidMount=`, { tag, CustomPopView });
377
+ debugLog(`AutoPositionedPopup componentDidMount=`, { tag, CustomPopView });
367
378
  //componentWillUnmount
368
379
  return () => {
369
- console.log(`AutoPositionedPopup componentWillUnmount tag=`, tag);
380
+ debugLog(`AutoPositionedPopup componentWillUnmount tag=`, tag);
370
381
  removeRootView(tag, forceRemoveAllRootViewOnItemSelected, rootViewsRef.current);
371
382
  setSearchQuery('');
372
383
  if (textInputRef.current) {
@@ -379,7 +390,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
379
390
  };
380
391
  }, []);
381
392
  useEffect(() => {
382
- console.log('AutoPositionedPopup rootViews=', { tag, rootViews });
393
+ debugLog('AutoPositionedPopup rootViews=', { tag, rootViews });
383
394
  rootViewsRef.current = rootViews;
384
395
  if (rootViews.length === 0) {
385
396
  hasAddedRootView.current = false;
@@ -394,23 +405,22 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
394
405
  }, [rootViews]);
395
406
  useEffect(() => {
396
407
  var _a, _b;
397
- console.log('AutoPositionedPopup useEffect [selectedItem, state.selectedItem, tag]=', { tag, selectedItem, 'state.selectedItem': state.selectedItem });
398
- console.log('AutoPositionedPopup useEffect state.selectedItem=', state.selectedItem);
408
+ debugLog('AutoPositionedPopup useEffect [selectedItem, state.selectedItem, tag]=', { tag, selectedItem, 'state.selectedItem': state.selectedItem });
409
+ debugLog('AutoPositionedPopup useEffect state.selectedItem=', state.selectedItem);
399
410
  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
- console.log('AutoPositionedPopup useEffect selectedItem!=state.selectedItem');
411
+ debugLog('AutoPositionedPopup useEffect selectedItem!=state.selectedItem');
401
412
  setState((prevState) => {
402
413
  return Object.assign(Object.assign({}, prevState), { selectedItem: selectedItem });
403
414
  });
404
415
  }
405
416
  }, [selectedItem, state.selectedItem, tag]);
406
417
  useEffect(() => {
407
- var _a;
408
418
  // Detect if keyboard state has actually changed to avoid false triggers during parent component re-renders
409
419
  const keyboardStateChanged = prevIsKeyboardFullyShownRef.current !== isKeyboardFullyShown;
410
420
  const propsChanged = prevPropsRef.current.CustomPopView !== CustomPopView ||
411
421
  prevPropsRef.current.CustomPopViewStyle !== CustomPopViewStyle ||
412
422
  (prevPropsRef.current.TextInputProps !== TextInputProps && useTextInput);
413
- console.log('AutoPositionedPopup useEffect [isKeyboardFullyShown,\n' +
423
+ debugLog('AutoPositionedPopup useEffect [isKeyboardFullyShown,\n' +
414
424
  ' state.isFocus,\n' +
415
425
  ' useTextInput,\n' +
416
426
  ' CustomPopView,\n' +
@@ -441,13 +451,18 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
441
451
  TextInputProps
442
452
  };
443
453
  // Only execute logic when keyboard state actually changes or user actively operates
444
- if (!keyboardStateChanged && hasAddedRootView.current) {
445
- console.log('AutoPositionedPopup: Skip execution - parent component re-rendered but keyboard state unchanged textInputRef.current=', textInputRef.current);
446
- // if (!ref_isFocus.current) {
447
- // textInputRef.current?.focus()
448
- // }
454
+ // CRITICAL FIX: Also allow execution when popup needs initial positioning
455
+ // hasAddedRootView.current = true means popup container exists
456
+ // hasShownRootView.current = false means positioning not done yet
457
+ // We MUST allow execution when popup needs positioning, even if keyboard state unchanged
458
+ if (!keyboardStateChanged && hasAddedRootView.current && hasShownRootView.current) {
459
+ debugLog('AutoPositionedPopup: Skip execution - already positioned and keyboard state unchanged');
449
460
  return;
450
461
  }
462
+ // Log when we're allowing execution for initial positioning
463
+ if (!keyboardStateChanged && hasAddedRootView.current && !hasShownRootView.current) {
464
+ debugLog('AutoPositionedPopup: ALLOWING execution for initial positioning (popup added but not positioned yet)');
465
+ }
451
466
  const getStatusBarHeight = () => {
452
467
  if (Platform.OS === 'android') {
453
468
  // Android: Use StatusBar.currentHeight API
@@ -468,7 +483,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
468
483
  // When keyboard appears, the trigger button may be covered. If parentScrollViewRef
469
484
  // is provided, scroll the parent to keep the trigger visible above the keyboard.
470
485
  if (parentScrollViewRef === null || parentScrollViewRef === void 0 ? void 0 : parentScrollViewRef.current) {
471
- console.log('AutoPositionedPopup: Keyboard appeared, scrolling parent to keep trigger visible');
486
+ debugLog('AutoPositionedPopup: Keyboard appeared, scrolling parent to keep trigger visible');
472
487
  // Use a slight delay to ensure keyboard animation has started
473
488
  setTimeout(() => {
474
489
  scrollToTriggerWithMeasure();
@@ -488,97 +503,142 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
488
503
  // then requestAnimationFrame ensures measurement happens after next render frame
489
504
  setTimeout(() => {
490
505
  requestAnimationFrame(() => {
491
- // CRITICAL FIX: Use triggerBtnRef (the actual TouchableOpacity) for measurement
492
- // instead of refAutoPositionedPopup (the outer View with flex:1/height:100%)
493
- const measureTarget = triggerBtnRef.current || refAutoPositionedPopup.current;
494
- measureTarget === null || measureTarget === void 0 ? void 0 : measureTarget.measureInWindow((x, y, width, height) => {
506
+ // CRITICAL FIX: Measure CURRENT position AFTER keyboard animation completes
507
+ // DO NOT use stored triggerPositionRef because keyboard may have shifted the view up
508
+ // Instead, measure the outer wrapper (refAutoPositionedPopup)
509
+ // which reflects the ACTUAL current position after keyboard shift
510
+ var _a;
511
+ // DEBUG: Log both refs to compare their positions
512
+ debugLog('AutoPositionedPopup DEBUG: refs status=', {
513
+ hasTextInputRef: !!textInputRef.current,
514
+ hasRefAutoPositionedPopup: !!refAutoPositionedPopup.current,
515
+ });
516
+ // Measure BOTH refs for comparison
517
+ if (textInputRef.current && refAutoPositionedPopup.current) {
518
+ textInputRef.current.measureInWindow((tx, ty, tw, th) => {
519
+ debugLog('AutoPositionedPopup DEBUG: textInputRef position=', { x: tx, y: ty, width: tw, height: th });
520
+ });
521
+ refAutoPositionedPopup.current.measureInWindow((rx, ry, rw, rh) => {
522
+ debugLog('AutoPositionedPopup DEBUG: refAutoPositionedPopup position=', { x: rx, y: ry, width: rw, height: rh });
523
+ });
524
+ }
525
+ // CRITICAL FIX: Use textInputRef as primary measurement target
526
+ // refAutoPositionedPopup.measureInWindow() returns undefined values
527
+ // because the outer wrapper View uses flex:1/height:100% which makes it unmeasurable
528
+ // textInputRef reliably returns the actual position of the input field
529
+ const measureTarget = textInputRef.current || refAutoPositionedPopup.current;
530
+ if (!measureTarget) {
531
+ debugLog('AutoPositionedPopup useTextInput: no measureTarget available, using fallback');
532
+ const screenHeightFallback = Dimensions.get('window').height;
533
+ const screenWidthFallback = Dimensions.get('window').width;
534
+ const fallbackY = (screenHeightFallback - listLayout.height) / 2;
535
+ ref_listPos.current = { x: screenWidthFallback * 0.05, y: fallbackY, width: screenWidthFallback * 0.9 };
536
+ updateRootView(tag, {
537
+ style: {
538
+ top: (_a = ref_listPos.current) === null || _a === void 0 ? void 0 : _a.y,
539
+ left: popUpViewStyle === null || popUpViewStyle === void 0 ? void 0 : popUpViewStyle.left,
540
+ width: popUpViewStyle === null || popUpViewStyle === void 0 ? void 0 : popUpViewStyle.width,
541
+ height: listLayout.height,
542
+ opacity: 1,
543
+ }
544
+ });
545
+ hasShownRootView.current = true;
546
+ return;
547
+ }
548
+ // Determine which ref is actually being used (for logging)
549
+ const usingTextInputRef = measureTarget === textInputRef.current;
550
+ debugLog('AutoPositionedPopup useTextInput: using measureTarget=', usingTextInputRef ? 'textInputRef' : 'refAutoPositionedPopup');
551
+ // Measure CURRENT position (after keyboard shifted the view)
552
+ measureTarget.measureInWindow((x, y, width, height) => {
495
553
  var _a;
496
- console.log('AutoPositionedPopup useTextInput measureInWindow (after 300ms + RAF, layout stable)=', { x, y, width, height, usingTriggerRef: !!triggerBtnRef.current });
497
- // CRITICAL FIX: Handle undefined values from measureInWindow
498
- // This can happen during navigation transitions or when view is not yet mounted
554
+ debugLog('AutoPositionedPopup useTextInput: measured position for positioning=', {
555
+ x, y, width, height,
556
+ measureTarget: usingTextInputRef ? 'textInputRef' : 'refAutoPositionedPopup'
557
+ });
558
+ // Handle undefined values (can happen during navigation transitions)
499
559
  if (x === undefined || y === undefined || width === undefined || height === undefined) {
500
- console.warn('AutoPositionedPopup useTextInput: measureInWindow returned undefined values, using fallback position');
560
+ debugLog('AutoPositionedPopup useTextInput: measureInWindow returned undefined, using fallback');
501
561
  const screenHeightFallback = Dimensions.get('window').height;
502
562
  const screenWidthFallback = Dimensions.get('window').width;
503
563
  const fallbackY = (screenHeightFallback - listLayout.height) / 2;
504
- const fallbackX = screenWidthFallback * 0.05;
505
- const fallbackWidth = screenWidthFallback * 0.9;
506
- x = fallbackX;
564
+ x = screenWidthFallback * 0.05;
507
565
  y = fallbackY;
508
- width = fallbackWidth;
566
+ width = screenWidthFallback * 0.9;
509
567
  height = 50;
510
568
  }
511
- // CRITICAL FIX: Coordinate system mismatch issue
512
- // Problem: measureInWindow returns coordinates relative to window (fixed reference),
513
- // but popup uses absolute positioning relative to App container (which shifts when keyboard appears)
514
- //
515
- // When keyboard appears:
516
- // 1. measureInWindow returns y relative to window (e.g., y=400 after shifting)
517
- // 2. But popup's absolute positioning is relative to App container
518
- // 3. If App container shifted up by 200px, setting top=200 will display at window.y=0 (wrong!)
519
- //
520
- // Solution: Since popup is rendered at root level and uses absolute positioning,
521
- // we should directly use measureInWindow's y value without additional calculations
522
- // The popup container is at the same level as the page content
523
- const screenHeight = Dimensions.get('window').height; // Use window height, not screen
524
- console.log('AutoPositionedPopup useTextInput positioning data=', {
569
+ // Calculate screen height and popup position
570
+ const screenHeight = Dimensions.get('window').height;
571
+ debugLog('AutoPositionedPopup useTextInput positioning data=', {
525
572
  screenHeight,
526
573
  componentY: y,
527
574
  componentHeight: height,
528
575
  listHeight: listLayout.height
529
576
  });
530
- // FIXED POSITIONING LOGIC (with keyboard):
531
- // measureInWindow returns coordinates relative to the window (screen)
532
- // The popup uses position: 'absolute' relative to RootViewProvider
533
- // So we should NOT add statusBarHeight to the position calculation
534
- //
535
- // 1. Default: show popup ABOVE the input field
536
- // Position popup so that the trigger remains VISIBLE below the popup
537
- // Use (y + height * 0.7) as reference to compensate for measurement offset
538
- // while still leaving trigger visible (30% of trigger height exposed)
539
- let popupY = y + (height * 0.7) - listLayout.height;
540
- console.log('AutoPositionedPopup with keyboard: initial calculation for ABOVE position:', {
541
- componentY: y,
542
- componentHeight: height,
577
+ // POSITIONING LOGIC (with keyboard):
578
+ // Simple rule: popup must TOUCH the trigger with NO GAP
579
+ // 1. Default: show ABOVE trigger (popup bottom touches trigger top)
580
+ // 2. If ABOVE would overlap status bar: show BELOW (popup top touches trigger bottom)
581
+ debugLog('AutoPositionedPopup POSITIONING:', {
582
+ triggerY: y,
583
+ triggerHeight: height,
584
+ triggerBottom: y + height,
543
585
  popupHeight: listLayout.height,
586
+ screenHeight,
587
+ statusBarHeight
588
+ });
589
+ // 1. Default: show popup ABOVE the trigger
590
+ // Popup has internal padding (12px from autoPositionedPopupList style)
591
+ // To make popup CONTENT touch trigger (not container), add padding offset
592
+ // Container bottom at y + POPUP_PADDING, content bottom at y (no gap)
593
+ const POPUP_PADDING = 12;
594
+ let popupY = y - listLayout.height + POPUP_PADDING;
595
+ let position = 'ABOVE';
596
+ debugLog('AutoPositionedPopup: trying ABOVE position:', {
544
597
  popupY,
545
598
  popupBottom: popupY + listLayout.height,
599
+ contentBottom: popupY + listLayout.height - POPUP_PADDING,
546
600
  triggerTop: y,
547
- statusBarHeight
601
+ paddingOffset: POPUP_PADDING,
602
+ wouldOverlapStatusBar: popupY < statusBarHeight
548
603
  });
549
- // 2. Check if showing above would go behind status bar
604
+ // 2. If showing ABOVE would go behind status bar, show BELOW instead
550
605
  if (popupY < statusBarHeight) {
551
- console.log('AutoPositionedPopup with keyboard: would go behind status bar, showing BELOW instead');
552
- // Show BELOW the input field
553
- // Since y + height represents the trigger's "reference bottom" (accounting for measurement offset),
554
- // we need to add another height to position popup BELOW the actual trigger
555
- popupY = y + height + height;
556
- console.log('AutoPositionedPopup with keyboard: BELOW position calculated:', {
557
- formula: 'y + 2*height',
558
- y,
559
- height,
560
- popupY
606
+ // Show BELOW: popup top at trigger bottom
607
+ // Use trigger's measured height as buffer to account for row padding
608
+ // The TextInput is only part of the trigger row - row height scales with trigger height
609
+ const BELOW_BUFFER = height;
610
+ popupY = y + height + BELOW_BUFFER;
611
+ position = 'BELOW';
612
+ debugLog('AutoPositionedPopup: using BELOW position (ABOVE overlaps status bar):', {
613
+ popupY,
614
+ triggerBottom: y + height,
615
+ buffer: BELOW_BUFFER,
616
+ actualGap: BELOW_BUFFER
561
617
  });
562
- // 3. Also check if showing below would go off the bottom
618
+ // 3. Safety check: if BELOW would go off screen bottom, clamp it
563
619
  const maxY = screenHeight - listLayout.height;
564
620
  if (popupY > maxY) {
565
- // If both positions are problematic, clamp to visible area
566
- console.log('AutoPositionedPopup with keyboard: both positions problematic, clamping to visible area');
567
- popupY = Math.min(Math.max(statusBarHeight, y - listLayout.height), maxY);
621
+ popupY = maxY;
622
+ debugLog('AutoPositionedPopup: clamped to screen bottom:', { popupY, maxY });
568
623
  }
569
624
  }
570
625
  else {
571
- console.log('AutoPositionedPopup with keyboard: showing ABOVE input field (preferred position)');
626
+ debugLog('AutoPositionedPopup: using ABOVE position (preferred)');
572
627
  }
628
+ debugLog('AutoPositionedPopup FINAL POSITION:', { position, popupY, touchesTrigger: true });
573
629
  ref_listPos.current = { x: x, y: popupY, width: width };
574
- console.log('AutoPositionedPopup useTextInput final position=', ref_listPos.current);
575
- setRootViewNativeStyle(tag, {
630
+ debugLog('AutoPositionedPopup useTextInput final position=', ref_listPos.current);
631
+ // Use updateRootView instead of setRootViewNativeStyle for more reliable style updates
632
+ // setNativeProps may not work correctly when initial style is in an array
633
+ const newStyle = {
576
634
  top: (_a = ref_listPos.current) === null || _a === void 0 ? void 0 : _a.y,
577
635
  left: popUpViewStyle === null || popUpViewStyle === void 0 ? void 0 : popUpViewStyle.left,
578
636
  width: popUpViewStyle === null || popUpViewStyle === void 0 ? void 0 : popUpViewStyle.width,
579
637
  height: listLayout.height,
580
638
  opacity: 1,
581
- });
639
+ };
640
+ debugLog('AutoPositionedPopup useTextInput: applying new style via updateRootView=', newStyle);
641
+ updateRootView(tag, { style: newStyle });
582
642
  hasShownRootView.current = true;
583
643
  });
584
644
  });
@@ -586,7 +646,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
586
646
  }
587
647
  else if (!isKeyboardFullyShown && ref_isFocus.current && keyboardStateChanged) {
588
648
  // Only execute close logic when keyboard state actually changes from true to false
589
- console.log('AutoPositionedPopup isKeyboardFullyShown useEffect removeRootView (keyboard state changed)=', { tag, forceRemoveAllRootViewOnItemSelected, keyboardStateChanged });
649
+ debugLog('AutoPositionedPopup isKeyboardFullyShown useEffect removeRootView (keyboard state changed)=', { tag, forceRemoveAllRootViewOnItemSelected, keyboardStateChanged });
590
650
  removeRootView(tag, forceRemoveAllRootViewOnItemSelected);
591
651
  setState((prevState) => {
592
652
  return Object.assign(Object.assign({}, prevState), { isFocus: false });
@@ -597,173 +657,52 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
597
657
  }
598
658
  }
599
659
  else {
660
+ // V17 SIMPLIFICATION: When useTextInput=false, ALWAYS show popup in CENTER of screen
661
+ // User request: "只要传入的 useTextInput 是 false, 弹框都显示在屏幕中间"
662
+ // This avoids all complex positioning calculations that kept failing
600
663
  if (state.isFocus) {
601
664
  if (isKeyboardFullyShown) {
602
665
  Keyboard.dismiss();
603
666
  return;
604
667
  }
605
- // CRITICAL FIX: Use triggerBtnRef (the actual TouchableOpacity) for measurement
606
- // instead of refAutoPositionedPopup (the outer View with flex:1/height:100%)
607
- // This ensures accurate position when component is inside complex layouts like KeyboardAwareScrollView
608
- const measureTarget = triggerBtnRef.current || refAutoPositionedPopup.current;
609
- measureTarget === null || measureTarget === void 0 ? void 0 : measureTarget.measureInWindow((x, y, width, height) => {
610
- console.log('AutoPositionedPopup !useTextInput measureInWindow=', { x, y, width, height, usingTriggerRef: !!triggerBtnRef.current });
611
- // CRITICAL FIX: Handle undefined values from measureInWindow
612
- // This can happen during navigation transitions or when view is not yet mounted
613
- if (x === undefined || y === undefined || width === undefined || height === undefined) {
614
- console.warn('AutoPositionedPopup: measureInWindow returned undefined values, using fallback position');
615
- // Use screen center as fallback position
616
- const screenHeight = Dimensions.get('window').height;
617
- const screenWidth = Dimensions.get('window').width;
618
- const fallbackY = (screenHeight - listLayout.height) / 2;
619
- const fallbackX = screenWidth * 0.05; // 5% from left
620
- const fallbackWidth = screenWidth * 0.9; // 90% width
621
- ref_listPos.current = { x: fallbackX, y: fallbackY, width: fallbackWidth };
622
- console.log('AutoPositionedPopup !useTextInput using fallback position=', ref_listPos.current);
623
- // Proceed with fallback values
624
- x = fallbackX;
625
- y = fallbackY;
626
- width = fallbackWidth;
627
- height = 50; // Default height for the trigger element
628
- }
629
- // CORRECT POSITIONING LOGIC (as per user requirement)
630
- // Default: show popup ABOVE the input field
631
- // Only if that goes off the top of screen (considering status bar), show BELOW instead
632
- const calculateOptimalPosition = (componentY, componentHeight, popupHeight) => {
633
- console.log('AutoPositionedPopup calculateOptimalPosition executing');
634
- // Use window height (visible area) instead of screen height
635
- const screenHeight = Dimensions.get('window').height;
636
- console.log('AutoPositionedPopup positioning data:', {
637
- screenHeight,
638
- componentY,
639
- componentHeight,
640
- popupHeight,
641
- statusBarHeight,
642
- platform: Platform.OS
643
- });
644
- // FIXED POSITIONING LOGIC:
645
- // The popup uses position: 'absolute' relative to the RootViewProvider container
646
- // measureInWindow returns coordinates relative to the window (screen)
647
- // So we should NOT add statusBarHeight to the position calculation
648
- //
649
- // 1. Default: show popup ABOVE the trigger element
650
- // FIX: Use (componentY + componentHeight) as the trigger's bottom edge reference point
651
- // This compensates for measurement inaccuracies when trigger is inside complex layouts (FlatList, ScrollView)
652
- // The popup's bottom should be at the trigger's top with minimal gap (≤5px)
653
- // Formula: popup_top = trigger_bottom - componentHeight - popupHeight
654
- // popup_bottom = trigger_bottom - componentHeight = trigger_top
655
- let popupY = componentY + componentHeight - popupHeight;
656
- console.log('AutoPositionedPopup: initial calculation for ABOVE position:', {
657
- componentY,
658
- componentHeight,
659
- popupHeight,
660
- popupY,
661
- triggerBottom: componentY + componentHeight,
662
- statusBarHeight
663
- });
664
- // 2. Check if showing above would go off the top of screen (behind status bar)
665
- if (popupY < statusBarHeight) {
666
- console.log('AutoPositionedPopup: would go behind status bar, showing BELOW instead');
667
- // Show BELOW the trigger element
668
- // Since componentY + componentHeight represents the trigger's "reference bottom" (accounting for measurement offset),
669
- // we need to add another componentHeight to position popup BELOW the actual trigger
670
- // Formula: popup top = componentY + (2 * componentHeight)
671
- // - (componentY + componentHeight) = trigger's actual top (compensated)
672
- // - + componentHeight = skip past trigger height to get to trigger's actual bottom
673
- popupY = componentY + componentHeight + componentHeight;
674
- console.log('AutoPositionedPopup: BELOW position calculated:', {
675
- formula: 'componentY + 2*componentHeight',
676
- componentY,
677
- componentHeight,
678
- popupY
679
- });
680
- // 3. Also check if showing below would go off the bottom of screen
681
- const maxY = screenHeight - popupHeight;
682
- if (popupY > maxY) {
683
- // If both positions are problematic, clamp to visible area
684
- // Prioritize showing as close to trigger as possible
685
- console.log('AutoPositionedPopup: both positions problematic, clamping to visible area');
686
- popupY = Math.min(Math.max(statusBarHeight, componentY - popupHeight), maxY);
687
- }
688
- }
689
- else {
690
- console.log('AutoPositionedPopup: showing ABOVE input field (preferred position)');
691
- }
692
- console.log('AutoPositionedPopup final position:', {
693
- popupY,
694
- 'showing above': popupY < componentY,
695
- 'below status bar': popupY >= statusBarHeight
696
- });
697
- return { finalY: popupY, showAbove: popupY < componentY };
698
- };
699
- // Calculate position ONCE based on actual popup height
700
- const actualPopupHeight = CustomPopView && CustomPopViewStyle && typeof CustomPopViewStyle.height === 'number'
701
- ? CustomPopViewStyle.height
702
- : listLayout.height;
703
- console.log('AutoPositionedPopup 🔥 Using actualPopupHeight for calculation:', { actualPopupHeight, CustomPopView: !!CustomPopView });
704
- const positionResult = calculateOptimalPosition(y, height, actualPopupHeight);
705
- console.log('AutoPositionedPopup FINAL position result:', positionResult);
706
- ref_listPos.current = { x: x, y: positionResult.finalY, width: width };
707
- console.log('AutoPositionedPopup !useTextInput ref_listPos.current=', ref_listPos.current);
708
- if (CustomPopView && CustomPopViewStyle) {
709
- // Position already calculated correctly above, no need to recalculate
710
- const PopViewComponent = CustomPopView();
711
- console.log('AutoPositionedPopup !useTextInput addRootView=', { CustomPopViewStyle, PopViewComponent, 'state.selectedItem': state.selectedItem });
712
- addRootView({
713
- id: tag,
714
- style: !centerDisplay
715
- ? Object.assign({ top: ref_listPos.current.y, left: popUpViewStyle === null || popUpViewStyle === void 0 ? void 0 : popUpViewStyle.left, width: popUpViewStyle === null || popUpViewStyle === void 0 ? void 0 : popUpViewStyle.width, height: listLayout.height, opacity: 1 }, CustomPopViewStyle) : Object.assign({ width: popUpViewStyle === null || popUpViewStyle === void 0 ? void 0 : popUpViewStyle.width, height: listLayout.height }, CustomPopViewStyle),
716
- component: <PopViewComponent selectedItem={state.selectedItem}></PopViewComponent>,
717
- useModal: true,
718
- onModalClose: () => {
719
- console.log('AutoPositionedPopup onModalClose');
720
- removeRootView(tag, forceRemoveAllRootViewOnItemSelected, rootViewsRef.current);
721
- setState((prevState) => {
722
- return Object.assign(Object.assign({}, prevState), { isFocus: false });
723
- });
724
- hasAddedRootView.current = false;
725
- hasShownRootView.current = false;
726
- hasTriggeredFocus.current = false;
727
- setSearchQuery('');
728
- },
729
- centerDisplay,
730
- });
731
- }
732
- else {
733
- console.log('AutoPositionedPopup !useTextInput addRootView tag=', tag);
734
- addRootView({
735
- id: tag,
736
- style: {
737
- top: ref_listPos.current.y,
738
- left: popUpViewStyle === null || popUpViewStyle === void 0 ? void 0 : popUpViewStyle.left,
739
- width: popUpViewStyle === null || popUpViewStyle === void 0 ? void 0 : popUpViewStyle.width,
740
- height: listLayout.height,
741
- opacity: 1,
742
- },
743
- component: (<AutoPositionedPopupList tag={tag} updateState={updateState} fetchData={fetchData} pageSize={pageSize} renderItem={renderItem} selectedItem={state.selectedItem} localSearch={localSearch} showListEmptyComponent={showListEmptyComponent} emptyText={emptyText} themeMode={themeMode}/>),
744
- useModal: true,
745
- onModalClose: () => {
746
- console.log('AutoPositionedPopup onModalClose tag=', tag);
747
- removeRootView(tag, forceRemoveAllRootViewOnItemSelected, rootViewsRef.current);
748
- setState((prevState) => {
749
- return Object.assign({}, prevState);
750
- });
751
- setSearchQuery('');
752
- },
753
- });
754
- }
755
- });
756
- }
757
- }
758
- if (isKeyboardFullyShown) {
759
- ref_isFocus.current = (_a = state.isFocus) !== null && _a !== void 0 ? _a : false;
760
- if (isKeyboardFullyShown !== keyboardVisibleRef.current) {
761
- keyboardVisibleRef.current = isKeyboardFullyShown;
762
- if (isKeyboardFullyShown && textInputRef.current) {
763
- if (ref_searchQuery.current) {
764
- textInputRef.current.setNativeProps({ text: ref_searchQuery.current });
765
- }
668
+ debugLog('🟢🟢🟢 POPUP_V17 useTextInput=false, showing popup in CENTER of screen');
669
+ const actualPopupHeight = CustomPopView && CustomPopViewStyle && typeof CustomPopViewStyle.height === 'number'
670
+ ? CustomPopViewStyle.height
671
+ : listLayout.height;
672
+ if (CustomPopView && CustomPopViewStyle) {
673
+ const PopViewComponent = CustomPopView();
674
+ debugLog('🔵🔵🔵 POPUP_V17 CustomPopView centerDisplay=true');
675
+ addRootView({
676
+ id: tag,
677
+ style: Object.assign({ width: popUpViewStyle === null || popUpViewStyle === void 0 ? void 0 : popUpViewStyle.width }, CustomPopViewStyle),
678
+ component: <PopViewComponent selectedItem={state.selectedItem}></PopViewComponent>,
679
+ useModal: true,
680
+ centerDisplay: true, // V17: Force center display for useTextInput=false
681
+ onModalClose: () => {
682
+ debugLog('AutoPositionedPopup V17 onModalClose tag=', tag);
683
+ removeRootView(tag, forceRemoveAllRootViewOnItemSelected, rootViewsRef.current);
684
+ setState((prevState) => (Object.assign({}, prevState)));
685
+ setSearchQuery('');
686
+ },
687
+ });
688
+ }
689
+ else {
690
+ debugLog('🔵🔵🔵 POPUP_V17 List centerDisplay=true, height=', listLayout.height);
691
+ addRootView({
692
+ id: tag,
693
+ style: { width: popUpViewStyle === null || popUpViewStyle === void 0 ? void 0 : popUpViewStyle.width, height: listLayout.height, opacity: 1 },
694
+ component: (<AutoPositionedPopupList tag={tag} updateState={updateState} fetchData={fetchData} pageSize={pageSize} renderItem={renderItem} selectedItem={state.selectedItem} localSearch={localSearch} showListEmptyComponent={showListEmptyComponent} emptyText={emptyText} themeMode={themeMode}/>),
695
+ useModal: true,
696
+ centerDisplay: true, // V17: Force center display for useTextInput=false
697
+ onModalClose: () => {
698
+ debugLog('AutoPositionedPopup V17 onModalClose tag=', tag);
699
+ removeRootView(tag, forceRemoveAllRootViewOnItemSelected, rootViewsRef.current);
700
+ setState((prevState) => (Object.assign({}, prevState)));
701
+ setSearchQuery('');
702
+ },
703
+ });
766
704
  }
705
+ return; // V17: Early return after handling !useTextInput case
767
706
  }
768
707
  }
769
708
  }, [isKeyboardFullyShown,
@@ -775,10 +714,15 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
775
714
  tag, TextInputProps,
776
715
  state.selectedItem, showListEmptyComponent, themeMode
777
716
  ]);
717
+ // V16: All positioning logic is now in the useEffect above (calculateOptimalPosition + processPosition)
718
+ // V16 FIX: Capture position in onPress callback BEFORE setState is called
719
+ // This ensures triggerPositionRef.current is set when useEffect runs
720
+ // Formula: top = componentY - popupHeight (popup bottom touches trigger top exactly)
721
+ debugLog('🟢 POPUP_MODULE_V16_LOADED - capturing position in onPress callback before setState');
778
722
  // Imperative handle for parent component access
779
723
  useImperativeHandle(parentRef, () => ({
780
724
  clearSelectedItem: () => {
781
- console.log('AutoPositionedPopup clearSelectedItem tag=', tag);
725
+ debugLog('AutoPositionedPopup clearSelectedItem tag=', tag);
782
726
  setState((prevState) => {
783
727
  return Object.assign(Object.assign({}, prevState), { selectedItem: undefined, isFocus: false });
784
728
  });
@@ -795,11 +739,11 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
795
739
  },
796
740
  }), []);
797
741
  const updateState = (key, value) => {
798
- console.log('AutoPositionedPopup updateState=', { key, value });
742
+ debugLog('AutoPositionedPopup updateState=', { key, value });
799
743
  setState((prevState) => (Object.assign(Object.assign({}, prevState), { [key]: value })));
800
744
  if (key === 'selectedItem' && onItemSelected) {
801
745
  onItemSelected(value);
802
- console.log('AutoPositionedPopup updateState onItemSelected rootViewsRef.current=', rootViewsRef.current);
746
+ debugLog('AutoPositionedPopup updateState onItemSelected rootViewsRef.current=', rootViewsRef.current);
803
747
  removeRootView(tag, forceRemoveAllRootViewOnItemSelected, rootViewsRef.current);
804
748
  hasAddedRootView.current = false;
805
749
  hasShownRootView.current = false;
@@ -832,17 +776,17 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
832
776
  // Only update when deep comparison detects real changes to avoid TextInput recreation due to reference changes during parent component redraws
833
777
  const stableInputStyle = useMemo(() => {
834
778
  if (!shallowEqual(stableInputStyleRef.current, inputStyle)) {
835
- console.log(`AutoPositionedPopup stableInputStyle: `, { tag, inputStyle, themeMode });
779
+ debugLog(`AutoPositionedPopup stableInputStyle: `, { tag, inputStyle, themeMode });
836
780
  stableInputStyleRef.current = inputStyle;
837
781
  }
838
782
  return stableInputStyleRef.current;
839
783
  }, [inputStyle, tag, themeMode]);
840
784
  const stableTextInputProps = useMemo(() => {
841
785
  if (!shallowEqual(stableTextInputPropsRef.current, TextInputProps)) {
842
- console.log(`AutoPositionedPopup TextInputProps deep change detected, updating stable reference - tag: ${tag}`);
786
+ debugLog(`AutoPositionedPopup TextInputProps deep change detected, updating stable reference - tag: ${tag}`);
843
787
  stableTextInputPropsRef.current = TextInputProps;
844
788
  }
845
- console.log('AutoPositionedPopup stableTextInputProps=', { tag, TextInputProps, 'stableTextInputPropsRef.current': stableTextInputPropsRef.current });
789
+ debugLog('AutoPositionedPopup stableTextInputProps=', { tag, TextInputProps, 'stableTextInputPropsRef.current': stableTextInputPropsRef.current });
846
790
  return stableTextInputPropsRef.current;
847
791
  }, [TextInputProps, tag]);
848
792
  // Use useCallback to stabilize onFocus and onBlur callback references
@@ -853,7 +797,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
853
797
  const handleTextInputFocus = useCallback(() => {
854
798
  const currentTime = Date.now();
855
799
  const timeSinceLastFocus = currentTime - lastFocusTimeRef.current;
856
- console.log('AutoPositionedPopup onFocus=', {
800
+ debugLog('AutoPositionedPopup onFocus=', {
857
801
  tag,
858
802
  'state.selectedItem': stateRef.current.selectedItem,
859
803
  'hasTriggeredFocus.current=': hasTriggeredFocus.current,
@@ -865,17 +809,17 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
865
809
  });
866
810
  // Prevent rapid repeated triggers (repeated events within 300ms are ignored)
867
811
  if (timeSinceLastFocus < 300) {
868
- console.log('AutoPositionedPopup onFocus: Skip - event triggered too quickly (< 300ms)');
812
+ debugLog('AutoPositionedPopup onFocus: Skip - event triggered too quickly (< 300ms)');
869
813
  return;
870
814
  }
871
815
  // Skip if keyboard is already open and focus has been handled
872
816
  if (isKeyboardFullyShown && hasTriggeredFocus.current) {
873
- console.log('AutoPositionedPopup onFocus: Skip - keyboard already open and focus handled');
817
+ debugLog('AutoPositionedPopup onFocus: Skip - keyboard already open and focus handled');
874
818
  return;
875
819
  }
876
820
  // Prevent concurrent processing
877
821
  if (isFocusEventProcessingRef.current) {
878
- console.log('AutoPositionedPopup onFocus: Skip - processing another focus event');
822
+ debugLog('AutoPositionedPopup onFocus: Skip - processing another focus event');
879
823
  return;
880
824
  }
881
825
  isFocusEventProcessingRef.current = true;
@@ -898,7 +842,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
898
842
  }, 100);
899
843
  }, [tag, isKeyboardFullyShown]); // Remove state.selectedItem, use stateRef instead
900
844
  const handleTextInputBlur = useCallback(() => {
901
- console.log('AutoPositionedPopup onBlur=', {
845
+ debugLog('AutoPositionedPopup onBlur=', {
902
846
  tag,
903
847
  'textInputRef.current': textInputRef.current,
904
848
  'isKeyboardFullyShown': isKeyboardFullyShown,
@@ -906,7 +850,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
906
850
  });
907
851
  // If keyboard is still open, this is a false trigger caused by parent component re-render, should not reset
908
852
  if (isKeyboardFullyShown && hasTriggeredFocus.current) {
909
- console.log('AutoPositionedPopup onBlur: Skip - keyboard still open, possibly caused by parent component re-render');
853
+ debugLog('AutoPositionedPopup onBlur: Skip - keyboard still open, possibly caused by parent component re-render');
910
854
  return;
911
855
  }
912
856
  // Only reset internal state, do not actively close keyboard
@@ -930,29 +874,29 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
930
874
  // Wrap TextInput independently in useMemo to recreate only when key props change
931
875
  // This avoids repeated ref callback triggers due to other props changes during parent component redraws
932
876
  const memoizedTextInput = useMemo(() => {
933
- console.log('AutoPositionedPopup memoizedTextInput=', { tag, useTextInput, 'state.isFocus': state.isFocus, stableTextInputProps });
877
+ debugLog('AutoPositionedPopup memoizedTextInput=', { tag, useTextInput, 'state.isFocus': state.isFocus, stableTextInputProps });
934
878
  if (!useTextInput || !state.isFocus) {
935
879
  return null;
936
880
  }
937
881
  return (<RNTextInput ref={(ref) => {
938
882
  // Monitor TextInput mounting and unmounting
939
883
  if (ref && !textInputRef.current) {
940
- console.log(`AutoPositionedPopup TextInput created/mounted - tag: ${tag}, ref:`, ref);
884
+ debugLog(`AutoPositionedPopup TextInput created/mounted - tag: ${tag}, ref:`, ref);
941
885
  }
942
886
  else if (!ref && textInputRef.current) {
943
- console.log(`AutoPositionedPopup TextInput unmounted - tag: ${tag}`);
887
+ debugLog(`AutoPositionedPopup TextInput unmounted - tag: ${tag}`);
944
888
  }
945
889
  else if (ref && textInputRef.current && ref !== textInputRef.current) {
946
- console.log(`AutoPositionedPopup TextInput replaced - tag: ${tag}, oldRef:`, textInputRef.current, 'newRef:', ref);
890
+ debugLog(`AutoPositionedPopup TextInput replaced - tag: ${tag}, oldRef:`, textInputRef.current, 'newRef:', ref);
947
891
  }
948
892
  textInputRef.current = ref;
949
893
  }} key={`textinput-${tag}`} style={[
950
894
  styles.inputStyle,
951
895
  stableInputStyle,
952
- (themeMode === 'dark' && { color: '#fff' })
896
+ (themeMode === 'dark' && { color: '#fff' }),
953
897
  ]} textAlign={stableTextInputProps && stableTextInputProps['textAlign'] || 'left'} multiline={stableTextInputProps && stableTextInputProps['multiline'] || false} numberOfLines={stableTextInputProps && stableTextInputProps['numberOfLines'] || 1} onChangeText={(searchQuery) => {
954
898
  ref_searchQuery.current = searchQuery;
955
- console.log('AutoPositionedPopup onChangeText rootViews=', rootViews);
899
+ debugLog('AutoPositionedPopup onChangeText rootViews=', rootViews);
956
900
  if (!localSearch) {
957
901
  if (debounceTimerRef.current) {
958
902
  clearTimeout(debounceTimerRef.current);
@@ -971,7 +915,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
971
915
  Keyboard.dismiss();
972
916
  }
973
917
  }} 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
- console.log('AutoPositionedPopup.tsx onSubmitEditing e.nativeEvent.text=', e.nativeEvent.text);
918
+ debugLog('AutoPositionedPopup.tsx onSubmitEditing e.nativeEvent.text=', e.nativeEvent.text);
975
919
  onSubmitEditing && onSubmitEditing(e);
976
920
  }}/>);
977
921
  }, [
@@ -990,11 +934,11 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
990
934
  // Render the component following project implementation
991
935
  return useMemo(() => {
992
936
  var _a;
993
- console.log('AutoPositionedPopup render tag=', tag); // Now safe - circular dependency fixed
937
+ debugLog('AutoPositionedPopup render tag=', tag); // Now safe - circular dependency fixed
994
938
  return (<CustomRow>
995
939
  <View style={[styles.contain, style]} ref={refAutoPositionedPopup}>
996
940
  {!state.isFocus || !useTextInput ? (<TouchableOpacity ref={triggerBtnRef} style={[styles.AutoPositionedPopupBtn, AutoPositionedPopupBtnStyle]} disabled={AutoPositionedPopupBtnDisabled} onPress={() => {
997
- console.log('AutoPositionedPopup onPress=', {
941
+ debugLog('AutoPositionedPopup onPress=', {
998
942
  tag,
999
943
  'state.isFocus': state.isFocus,
1000
944
  useTextInput,
@@ -1006,10 +950,13 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
1006
950
  });
1007
951
  // Capture trigger button position BEFORE switching to TextInput
1008
952
  // This is critical because triggerBtnRef will become null after isFocus=true
1009
- if (triggerBtnRef.current && (parentScrollViewRef === null || parentScrollViewRef === void 0 ? void 0 : parentScrollViewRef.current)) {
953
+ // IMPORTANT: Always capture position regardless of parentScrollViewRef
954
+ if (triggerBtnRef.current) {
1010
955
  triggerBtnRef.current.measureInWindow((x, y, width, height) => {
1011
- console.log('AutoPositionedPopup onPress: captured trigger position=', { tag, x, y, width, height });
1012
- triggerPositionRef.current = { x, y, width, height };
956
+ debugLog('AutoPositionedPopup onPress: captured trigger position=', { tag, x, y, width, height });
957
+ if (x !== undefined && y !== undefined && width !== undefined && height !== undefined) {
958
+ triggerPositionRef.current = { x, y, width, height };
959
+ }
1013
960
  });
1014
961
  }
1015
962
  if (useTextInput) {
@@ -1049,11 +996,14 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
1049
996
  }
1050
997
  }
1051
998
  else {
999
+ // V17 SIMPLIFICATION: For useTextInput=false, popup will be centered
1000
+ // No need for complex position measurement - just trigger focus
1001
+ debugLog('🔵🔵🔵 POPUP_V17 onPress useTextInput=false, will show centered popup');
1052
1002
  setState((prevState) => {
1053
1003
  return Object.assign(Object.assign({}, prevState), { isFocus: true });
1054
1004
  });
1055
1005
  }
1056
- console.log('AutoPositionedPopup onPress done');
1006
+ debugLog('AutoPositionedPopup onPress done');
1057
1007
  }}>
1058
1008
  {!btwChildren ? (<Text style={[
1059
1009
  styles.searchQueryTxt,
@@ -1067,29 +1017,29 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
1067
1017
  </CustomRow>);
1068
1018
  }, [
1069
1019
  tag,
1070
- // CRITICAL FIX: Remove all props that may change frequently or are inline functions
1020
+ // �?CRITICAL FIX: Remove all props that may change frequently or are inline functions
1071
1021
  // Changes to these props should not cause the entire component tree to recreate, especially TextInput
1072
- // fetchData, // Removed: inline function
1073
- // renderItem, // Removed: possibly inline function
1074
- // onItemSelected, // Removed: possibly inline function
1075
- // onSubmitEditing, // Removed: possibly inline function
1022
+ // fetchData, // �?Removed: inline function
1023
+ // renderItem, // �?Removed: possibly inline function
1024
+ // onItemSelected, // �?Removed: possibly inline function
1025
+ // onSubmitEditing, // �?Removed: possibly inline function
1076
1026
  localSearch,
1077
- // placeholder, // Removed: may change
1078
- // textAlign, // Removed: may change
1027
+ // placeholder, // �?Removed: may change
1028
+ // textAlign, // �?Removed: may change
1079
1029
  pageSize,
1080
1030
  selectedItem,
1081
- // CustomRow, // Removed: inline function, new reference each time
1031
+ // CustomRow, // �?Removed: inline function, new reference each time
1082
1032
  useTextInput,
1083
- // btwChildren, // Removed: inline function
1084
- // keyExtractor, // Removed: possibly inline function
1085
- // AutoPositionedPopupBtnStyle, // Removed: possibly inline object
1086
- // CustomPopView, // Removed: may change
1087
- // CustomPopViewStyle, // Removed: may change
1033
+ // btwChildren, // �?Removed: inline function
1034
+ // keyExtractor, // �?Removed: possibly inline function
1035
+ // AutoPositionedPopupBtnStyle, // �?Removed: possibly inline object
1036
+ // CustomPopView, // �?Removed: may change
1037
+ // CustomPopViewStyle, // �?Removed: may change
1088
1038
  forceRemoveAllRootViewOnItemSelected,
1089
1039
  state.isFocus,
1090
1040
  showListEmptyComponent,
1091
1041
  emptyText,
1092
- // Removed most dependencies that may cause re-rendering, keeping only core dependencies that truly affect component structure
1042
+ // �?Removed most dependencies that may cause re-rendering, keeping only core dependencies that truly affect component structure
1093
1043
  // This prevents TextInput recreation due to inline functions/objects during parent component redraws
1094
1044
  ]);
1095
1045
  }));