react-native-auto-positioned-popup 1.0.11 → 1.0.13

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.
@@ -34,8 +34,7 @@ const ListItem = memo(({ updateState, item, index, selectedItem, }) => {
34
34
  rootViewsRef.current = rootViews;
35
35
  }, [rootViews]);
36
36
  return useMemo(() => {
37
- // console.log('AutoPositionedPopup.tsx ListItem index=', index);
38
- // console.log('AutoPositionedPopup.tsx ListItem item=', item);
37
+ // console.log('AutoPositionedPopup.tsx ListItem=', {index, item, selectedItem});
39
38
  const isSelected = item.id === (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.id);
40
39
  return (<TouchableOpacity key={item.id} style={[
41
40
  styles.commonModalRow,
@@ -51,7 +50,7 @@ const ListItem = memo(({ updateState, item, index, selectedItem, }) => {
51
50
  </TouchableOpacity>);
52
51
  }, [updateState, item, index, selectedItem, rootViewsRef]);
53
52
  });
54
- const AutoPositionedPopupList = memo(({ tag, updateState, fetchData, keyExtractor = (item) => String(item.id), renderItem, selectedItem, localSearch, pageSize, }) => {
53
+ const AutoPositionedPopupList = memo(({ tag, updateState, fetchData, keyExtractor = (item) => String(item.id), renderItem, selectedItem, localSearch, pageSize, showListEmptyComponent, emptyText }) => {
55
54
  const [state, setState] = useState({
56
55
  selectedItem: selectedItem,
57
56
  localData: [],
@@ -78,8 +77,8 @@ const AutoPositionedPopupList = memo(({ tag, updateState, fetchData, keyExtracto
78
77
  };
79
78
  }, []);
80
79
  // useEffect(() => {
81
- // // 監聽 TextInput 事件,收到就刷新列表,不依賴 global searchQuery
82
- // // 將最新的 searchQuery 同步到 list 專用的 ref,供 _fetchData 使用
80
+ // // Listen to TextInput events, refresh list when received, not dependent on global searchQuery
81
+ // // Sync the latest searchQuery to list-specific ref for _fetchData to use
83
82
  // ref_searchQuery.current = searchQuery;
84
83
  // console.log('AutoPositionedPopupList useEffect searchQuery=', searchQuery);
85
84
  // console.log('AutoPositionedPopupList useEffect state.localData=', state.localData);
@@ -108,10 +107,7 @@ const AutoPositionedPopupList = memo(({ tag, updateState, fetchData, keyExtracto
108
107
  updateState(key, value);
109
108
  };
110
109
  const _fetchData = async ({ pageIndex, pageSize: currentPageSize, }) => {
111
- console.log('AutoPositionedPopupList _fetchData pageIndex=', pageIndex, ' pageSize=', currentPageSize);
112
- console.log('AutoPositionedPopupList _fetchData state.localData=', state.localData);
113
- console.log('AutoPositionedPopupList _fetchData ref_searchQuery.current=', ref_searchQuery.current);
114
- console.log('AutoPositionedPopupList _fetchData localSearch=', localSearch);
110
+ console.log('AutoPositionedPopupList _fetchData=', { pageIndex, pageSize: currentPageSize, 'state.localData': state.localData, 'ref_searchQuery.current': ref_searchQuery.current, localSearch });
115
111
  if (localSearch && state.localData.length > 0) {
116
112
  const result = state.localData.filter((item) => {
117
113
  var _a;
@@ -160,7 +156,7 @@ const AutoPositionedPopupList = memo(({ tag, updateState, fetchData, keyExtracto
160
156
  // Babel configuration handles the path redirection based on global.$fake
161
157
  // No need for conditional import here
162
158
  return (<View style={[styles.baseModalView, styles.autoPositionedPopupList]}>
163
- <AdvancedFlatList style={[{ borderRadius: 0 }]} {...(ref_list && { ref: ref_list })} keyExtractor={(item, index) => keyExtractor ? keyExtractor(item) : item.id} keyboardShouldPersistTaps={'always'} fetchData={_fetchData} renderItem={renderItem ? ({ item, index }) => renderItem({ item: item, index }) : ({ item, index }) => _renderItem({ item: item, index })} showListEmptyComponent={false}/>
159
+ <AdvancedFlatList style={[{ borderRadius: 0 }]} {...(ref_list && { ref: ref_list })} keyExtractor={(item, index) => keyExtractor ? keyExtractor(item) : item.id} keyboardShouldPersistTaps={'always'} fetchData={_fetchData} renderItem={renderItem ? ({ item, index }) => renderItem({ item: item, index }) : ({ item, index }) => _renderItem({ item: item, index })} showListEmptyComponent={showListEmptyComponent} emptyText={emptyText}/>
164
160
  </View>);
165
161
  }, [tag,
166
162
  updateState,
@@ -172,7 +168,7 @@ const AutoPositionedPopupList = memo(({ tag, updateState, fetchData, keyExtracto
172
168
  searchQuery,
173
169
  localSearch,
174
170
  pageSize,
175
- rootViewsRef,
171
+ rootViewsRef, showListEmptyComponent, emptyText
176
172
  ]);
177
173
  });
178
174
  // List layout constants
@@ -182,7 +178,7 @@ const listLayout = {
182
178
  // Main AutoPositionedPopup component
183
179
  const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
184
180
  console.log('AutoPositionedPopup props=', props);
185
- const { tag, style, AutoPositionedPopupBtnStyle, placeholder = 'Please Select', onSubmitEditing, TextInputProps = {}, inputStyle, labelStyle, popUpViewStyle = { left: '5%', width: '90%' }, fetchData = async ({ pageIndex, pageSize, searchQuery, }) => {
181
+ const { tag, style, AutoPositionedPopupBtnStyle, placeholder = 'Please Select', onSubmitEditing, TextInputProps = { autoFocus: true }, inputStyle, labelStyle, popUpViewStyle = { left: '5%', width: '90%' }, fetchData = async ({ pageIndex, pageSize, searchQuery, }) => {
186
182
  const res = {
187
183
  items: [],
188
184
  pageIndex,
@@ -198,7 +194,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
198
194
  console.warn('Error in fetch operation:', e);
199
195
  }
200
196
  return res;
201
- }, 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)', textAlign = 'right', CustomPopView = undefined, CustomPopViewStyle } = props;
197
+ }, 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)', textAlign = 'right', CustomPopView = undefined, CustomPopViewStyle, showListEmptyComponent = true, emptyText = '' } = props;
202
198
  // State management similar to project implementation
203
199
  const [state, setState] = useState({
204
200
  isFocus: false,
@@ -218,6 +214,16 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
218
214
  const keyboardVisibleRef = useRef(false);
219
215
  const refAutoPositionedPopup = useRef(null);
220
216
  const ref_searchQuery = useRef('');
217
+ // Add ref to track previous keyboard state to avoid false triggers during parent component re-renders
218
+ const prevIsKeyboardFullyShownRef = useRef(false);
219
+ const prevPropsRef = useRef({});
220
+ // Add ref to prevent onFocus/onBlur loop triggers during parent component re-renders
221
+ const lastFocusTimeRef = useRef(0);
222
+ const isFocusEventProcessingRef = useRef(false);
223
+ // Add ref to stabilize TextInput props reference
224
+ // Only update when deep comparison detects real changes to avoid TextInput recreation due to reference changes during parent component redraws
225
+ const stableInputStyleRef = useRef(inputStyle);
226
+ const stableTextInputPropsRef = useRef(TextInputProps);
221
227
  // Simple keyboard status tracking (alternative to useKeyboardStatus hook)
222
228
  // Legacy state for compatibility
223
229
  const [isVisible, setIsVisible] = useState(false);
@@ -239,8 +245,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
239
245
  useEffect(() => {
240
246
  (async () => {
241
247
  })();
242
- console.log(`AutoPositionedPopup componentDidMount tag=`, tag);
243
- console.log('AutoPositionedPopup componentDidMount CustomPopView=', CustomPopView);
248
+ console.log(`AutoPositionedPopup componentDidMount=`, { tag, CustomPopView });
244
249
  //componentWillUnmount
245
250
  return () => {
246
251
  console.log(`AutoPositionedPopup componentWillUnmount tag=`, tag);
@@ -271,8 +276,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
271
276
  }, [rootViews]);
272
277
  useEffect(() => {
273
278
  var _a, _b;
274
- console.log('AutoPositionedPopup useEffect tag=', tag);
275
- console.log('AutoPositionedPopup useEffect selectedItem=', selectedItem);
279
+ console.log('AutoPositionedPopup useEffect [selectedItem, state.selectedItem, tag]=', { tag, selectedItem, 'state.selectedItem': state.selectedItem });
276
280
  console.log('AutoPositionedPopup useEffect state.selectedItem=', state.selectedItem);
277
281
  if (((_a = state.selectedItem) === null || _a === void 0 ? void 0 : _a.id) !== (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.id) || ((_b = state.selectedItem) === null || _b === void 0 ? void 0 : _b.title) !== (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.title)) {
278
282
  console.log('AutoPositionedPopup useEffect selectedItem!=state.selectedItem');
@@ -283,24 +287,55 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
283
287
  }, [selectedItem, state.selectedItem, tag]);
284
288
  useEffect(() => {
285
289
  var _a, _b, _c;
286
- console.log('AutoPositionedPopup useEffect tag=', tag);
287
- console.log('AutoPositionedPopup useEffect state.isFocus=', state.isFocus);
288
- console.log('AutoPositionedPopup useEffect isKeyboardFullyShown=', isKeyboardFullyShown);
289
- console.log('AutoPositionedPopup useEffect ref_isFocus.current=', ref_isFocus.current);
290
- console.log('AutoPositionedPopup useEffect ref_isKeyboardFullyShown.current=', ref_isKeyboardFullyShown.current);
291
- console.log('AutoPositionedPopup useEffect useTextInput=', useTextInput);
292
- console.log('AutoPositionedPopup useEffect TextInputProps=', TextInputProps);
293
- console.log('AutoPositionedPopup useEffect hasAddedRootView.current=', hasAddedRootView.current);
294
- console.log('AutoPositionedPopup useEffect hasShownRootView.current=', hasShownRootView.current);
290
+ // Detect if keyboard state has actually changed to avoid false triggers during parent component re-renders
291
+ const keyboardStateChanged = prevIsKeyboardFullyShownRef.current !== isKeyboardFullyShown;
292
+ const propsChanged = prevPropsRef.current.CustomPopView !== CustomPopView ||
293
+ prevPropsRef.current.CustomPopViewStyle !== CustomPopViewStyle ||
294
+ prevPropsRef.current.TextInputProps !== TextInputProps;
295
+ console.log('AutoPositionedPopup useEffect [isKeyboardFullyShown,\n' +
296
+ ' state.isFocus,\n' +
297
+ ' useTextInput,\n' +
298
+ ' CustomPopView,\n' +
299
+ ' CustomPopViewStyle,\n' +
300
+ ' forceRemoveAllRootViewOnItemSelected,\n' +
301
+ ' tag, TextInputProps,\n' +
302
+ ' state.selectedItem, showListEmptyComponent\n' +
303
+ ' ]=', {
304
+ tag,
305
+ 'state.isFocus': state.isFocus,
306
+ isKeyboardFullyShown,
307
+ 'ref_isFocus.current': ref_isFocus.current,
308
+ 'ref_isKeyboardFullyShown.current': ref_isKeyboardFullyShown.current,
309
+ useTextInput, TextInputProps,
310
+ 'hasAddedRootView.current': hasAddedRootView.current,
311
+ 'hasShownRootView.current': hasShownRootView.current,
312
+ 'keyboardStateChanged': keyboardStateChanged,
313
+ 'propsChanged': propsChanged
314
+ });
315
+ // Update ref to record current state
316
+ prevIsKeyboardFullyShownRef.current = isKeyboardFullyShown;
317
+ prevPropsRef.current = {
318
+ CustomPopView,
319
+ CustomPopViewStyle,
320
+ TextInputProps
321
+ };
322
+ // Only execute logic when keyboard state actually changes or user actively operates
323
+ if (!keyboardStateChanged && hasAddedRootView.current) {
324
+ console.log('AutoPositionedPopup: Skip execution - parent component re-rendered but keyboard state unchanged');
325
+ // if (!ref_isFocus.current) {
326
+ // textInputRef.current?.focus()
327
+ // }
328
+ return;
329
+ }
295
330
  if (useTextInput) {
296
331
  if (isKeyboardFullyShown && hasAddedRootView.current && !hasShownRootView.current && state.isFocus) {
297
332
  (_a = refAutoPositionedPopup.current) === null || _a === void 0 ? void 0 : _a.measureInWindow((x, y, width, height) => {
298
333
  var _a;
299
- console.log('AutoPositionedPopup measureInWindow x=', x, ' y=', y, ' width=', width, ' height=', height);
334
+ console.log('AutoPositionedPopup useTextInput measureInWindow=', { x, y, width, height });
300
335
  // SIMPLE CENTER-BASED POSITIONING STRATEGY
301
336
  const screenHeight = Dimensions.get('screen').height;
302
337
  const screenCenter = screenHeight / 2;
303
- console.log('AutoPositionedPopup screenHeight=', screenHeight, ' screenCenter=', screenCenter, ' componentY=', y);
338
+ console.log('AutoPositionedPopup useTextInput measureInWindow =', { screenHeight, screenCenter, componentY: y });
304
339
  // Simple rule: if component Y > screen center, show popup above; otherwise show below
305
340
  if (y > screenCenter) {
306
341
  console.log('AutoPositionedPopup with keyboard: showing above (Y > center)');
@@ -310,7 +345,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
310
345
  console.log('AutoPositionedPopup with keyboard: showing below (Y <= center)');
311
346
  ref_listPos.current = { x: x, y: y + height, width: width };
312
347
  }
313
- console.log('AutoPositionedPopup ref_listPos.current=', ref_listPos.current);
348
+ console.log('AutoPositionedPopup useTextInput ref_listPos.current=', ref_listPos.current);
314
349
  setRootViewNativeStyle(tag, {
315
350
  top: (_a = ref_listPos.current) === null || _a === void 0 ? void 0 : _a.y,
316
351
  left: popUpViewStyle === null || popUpViewStyle === void 0 ? void 0 : popUpViewStyle.left,
@@ -321,8 +356,9 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
321
356
  hasShownRootView.current = true;
322
357
  });
323
358
  }
324
- else if (!isKeyboardFullyShown && ref_isFocus.current) {
325
- console.log('AutoPositionedPopup isKeyboardFullyShown useEffect removeRootView tag=', tag, ' forceRemoveAllRootViewOnItemSelected=', forceRemoveAllRootViewOnItemSelected);
359
+ else if (!isKeyboardFullyShown && ref_isFocus.current && keyboardStateChanged) {
360
+ // Only execute close logic when keyboard state actually changes from true to false
361
+ console.log('AutoPositionedPopup isKeyboardFullyShown useEffect removeRootView (keyboard state changed)=', { tag, forceRemoveAllRootViewOnItemSelected, keyboardStateChanged });
326
362
  removeRootView(tag, forceRemoveAllRootViewOnItemSelected);
327
363
  setState((prevState) => {
328
364
  return Object.assign(Object.assign({}, prevState), { isFocus: false });
@@ -335,10 +371,10 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
335
371
  else {
336
372
  if (state.isFocus) {
337
373
  (_b = refAutoPositionedPopup.current) === null || _b === void 0 ? void 0 : _b.measureInWindow((x, y, width, height) => {
338
- console.log('AutoPositionedPopup measureInWindow x=', x, ' y=', y, ' width=', width, ' height=', height);
374
+ console.log('AutoPositionedPopup !useTextInput measureInWindow=', { x, y, width, height });
339
375
  // INTELLIGENT POSITION CALCULATION - MODIFIED VERSION WITH STATUS BAR SAFETY
340
376
  const calculateOptimalPosition = (componentY, componentHeight, popupHeight) => {
341
- console.log('🔥🔥🔥 NEW CALCULATE OPTIMAL POSITION FUNCTION EXECUTING 🔥🔥🔥');
377
+ console.log('AutoPositionedPopup 🔥🔥🔥 NEW CALCULATE OPTIMAL POSITION FUNCTION EXECUTING 🔥🔥🔥');
342
378
  // Use window height (visible area) instead of screen height (includes status bar)
343
379
  const windowHeight = Dimensions.get('window').height;
344
380
  const visibleAreaCenter = windowHeight / 2;
@@ -355,7 +391,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
355
391
  }
356
392
  };
357
393
  const statusBarHeight = getStatusBarHeight();
358
- console.log('🔥 Cross-platform StatusBar height:', statusBarHeight, 'Platform:', Platform.OS);
394
+ console.log('AutoPositionedPopup 🔥 Cross-platform StatusBar height:', statusBarHeight, 'Platform:', Platform.OS);
359
395
  // Calculate component center point as requested
360
396
  const componentCenterY = componentY + componentHeight / 2;
361
397
  console.log('AutoPositionedPopup positioning data:', {
@@ -403,7 +439,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
403
439
  // Strong platform adjustment - much smaller for Android bottom components
404
440
  const platformMultiplier = Platform.OS === 'ios' ? 1.0 : (isInBottomHalf ? 0.5 : 0.9);
405
441
  const finalSpacing = Math.max(baseSpacing, relativeSpacing) * edgeProximityFactor * platformMultiplier;
406
- console.log('🔥 Advanced spacing calculation:', {
442
+ console.log('AutoPositionedPopup 🔥 Advanced spacing calculation:', {
407
443
  componentCenter,
408
444
  screenCenter,
409
445
  distanceFromCenter,
@@ -439,7 +475,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
439
475
  // Component in bottom half + enough space above = FORCE ABOVE
440
476
  showAbove = true;
441
477
  finalY = componentY - popupHeight + componentHeight / 2;
442
- console.log('🔥 AutoPositionedPopup: FORCE ABOVE - bottom half component with enough space, finalY=', finalY);
478
+ console.log('AutoPositionedPopup 🔥 AutoPositionedPopup: FORCE ABOVE - bottom half component with enough space, finalY=', finalY);
443
479
  }
444
480
  else if (!isInBottomHalf && spaceBelow >= popupHeight) {
445
481
  // Component in top half + enough space below = show below
@@ -473,7 +509,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
473
509
  }
474
510
  }
475
511
  // Enhanced boundary check with detailed logging
476
- console.log('🔥 Pre-boundary check:', {
512
+ console.log('AutoPositionedPopup 🔥 Pre-boundary check:', {
477
513
  originalFinalY: finalY,
478
514
  showAbove,
479
515
  statusBarHeight,
@@ -485,21 +521,21 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
485
521
  if (showAbove && finalY < statusBarHeight) {
486
522
  const oldFinalY = finalY;
487
523
  finalY = statusBarHeight;
488
- console.log('🔥 BOUNDARY FIX: Above display adjusted for status bar:', oldFinalY, '->', finalY);
524
+ console.log('AutoPositionedPopup 🔥 BOUNDARY : Above display adjusted for status bar:', oldFinalY, '->', finalY);
489
525
  }
490
526
  if (!showAbove && finalY + popupHeight > windowHeight) {
491
527
  const oldFinalY = finalY;
492
528
  finalY = windowHeight - popupHeight;
493
- console.log('🔥 BOUNDARY FIX: Below display adjusted to fit window:', oldFinalY, '->', finalY);
529
+ console.log('AutoPositionedPopup 🔥 BOUNDARY : Below display adjusted to fit window:', oldFinalY, '->', finalY);
494
530
  }
495
531
  // CRITICAL CHECK: Detect if boundary check is changing display direction
496
532
  if (showAbove && finalY + popupHeight > componentY) {
497
- console.log('🚨 WARNING: Above positioning may overlap with component!');
533
+ console.log('AutoPositionedPopup 🚨 WARNING: Above positioning may overlap with component!');
498
534
  }
499
535
  if (!showAbove && finalY < componentY + componentHeight) {
500
- console.log('🚨 WARNING: Below positioning may overlap with component!');
536
+ console.log('AutoPositionedPopup 🚨 WARNING: Below positioning may overlap with component!');
501
537
  }
502
- console.log('🔥 Post-boundary check final result:', {
538
+ console.log('AutoPositionedPopup 🔥 Post-boundary check final result:', {
503
539
  finalY,
504
540
  showAbove,
505
541
  'popupTop': finalY,
@@ -513,17 +549,15 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
513
549
  const actualPopupHeight = CustomPopView && CustomPopViewStyle && typeof CustomPopViewStyle.height === 'number'
514
550
  ? CustomPopViewStyle.height
515
551
  : listLayout.height;
516
- console.log('🔥 Using actualPopupHeight for calculation:', actualPopupHeight, 'CustomPopView:', !!CustomPopView);
552
+ console.log('AutoPositionedPopup 🔥 Using actualPopupHeight for calculation:', { actualPopupHeight, CustomPopView: !!CustomPopView });
517
553
  const positionResult = calculateOptimalPosition(y, height, actualPopupHeight);
518
554
  console.log('AutoPositionedPopup FINAL position result:', positionResult);
519
555
  ref_listPos.current = { x: x, y: positionResult.finalY, width: width };
520
- console.log('AutoPositionedPopup ref_listPos.current=', ref_listPos.current);
556
+ console.log('AutoPositionedPopup !useTextInput ref_listPos.current=', ref_listPos.current);
521
557
  if (CustomPopView && CustomPopViewStyle) {
522
- console.log('AutoPositionedPopup CustomPopViewStyle=', CustomPopViewStyle);
523
558
  // Position already calculated correctly above, no need to recalculate
524
559
  const PopViewComponent = CustomPopView();
525
- console.log('AutoPositionedPopup addRootView PopViewComponent=', PopViewComponent);
526
- console.log('AutoPositionedPopup addRootView state.selectedItem=', state.selectedItem);
560
+ console.log('AutoPositionedPopup !useTextInput addRootView=', { CustomPopViewStyle, PopViewComponent, 'state.selectedItem': state.selectedItem });
527
561
  addRootView({
528
562
  id: tag,
529
563
  style: !centerDisplay
@@ -545,7 +579,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
545
579
  });
546
580
  }
547
581
  else {
548
- console.log('AutoPositionedPopup addRootView tag=', tag);
582
+ console.log('AutoPositionedPopup !useTextInput addRootView tag=', tag);
549
583
  addRootView({
550
584
  id: tag,
551
585
  style: {
@@ -555,7 +589,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
555
589
  height: listLayout.height,
556
590
  opacity: 1,
557
591
  },
558
- component: (<AutoPositionedPopupList tag={tag} updateState={updateState} fetchData={fetchData} pageSize={pageSize} renderItem={renderItem} selectedItem={state.selectedItem} localSearch={localSearch}/>),
592
+ component: (<AutoPositionedPopupList tag={tag} updateState={updateState} fetchData={fetchData} pageSize={pageSize} renderItem={renderItem} selectedItem={state.selectedItem} localSearch={localSearch} showListEmptyComponent={showListEmptyComponent} emptyText={emptyText}/>),
559
593
  useModal: true,
560
594
  onModalClose: () => {
561
595
  console.log('AutoPositionedPopup onModalClose tag=', tag);
@@ -587,8 +621,8 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
587
621
  CustomPopView,
588
622
  CustomPopViewStyle,
589
623
  forceRemoveAllRootViewOnItemSelected,
590
- tag,
591
- state.selectedItem,
624
+ tag, TextInputProps,
625
+ state.selectedItem, showListEmptyComponent
592
626
  ]);
593
627
  // Imperative handle for parent component access
594
628
  useImperativeHandle(parentRef, () => ({
@@ -600,7 +634,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
600
634
  },
601
635
  }), []);
602
636
  const updateState = (key, value) => {
603
- console.log('AutoPositionedPopup updateState key=', key, ' value=', value);
637
+ console.log('AutoPositionedPopup updateState=', { key, value });
604
638
  setState((prevState) => (Object.assign(Object.assign({}, prevState), { [key]: value })));
605
639
  if (key === 'selectedItem' && onItemSelected) {
606
640
  onItemSelected(value);
@@ -615,6 +649,179 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
615
649
  setSearchQuery('');
616
650
  }
617
651
  };
652
+ // Simple deep comparison function (for style objects only)
653
+ const shallowEqual = (obj1, obj2) => {
654
+ if (obj1 === obj2)
655
+ return true;
656
+ if (!obj1 || !obj2)
657
+ return false;
658
+ if (typeof obj1 !== 'object' || typeof obj2 !== 'object')
659
+ return false;
660
+ const keys1 = Object.keys(obj1);
661
+ const keys2 = Object.keys(obj2);
662
+ if (keys1.length !== keys2.length)
663
+ return false;
664
+ for (const key of keys1) {
665
+ if (obj1[key] !== obj2[key])
666
+ return false;
667
+ }
668
+ return true;
669
+ };
670
+ // Use useMemo to create stable props reference
671
+ // Only update when deep comparison detects real changes to avoid TextInput recreation due to reference changes during parent component redraws
672
+ const stableInputStyle = useMemo(() => {
673
+ if (!shallowEqual(stableInputStyleRef.current, inputStyle)) {
674
+ console.log(`AutoPositionedPopup inputStyle deep change detected, updating stable reference - tag: ${tag}`);
675
+ stableInputStyleRef.current = inputStyle;
676
+ }
677
+ return stableInputStyleRef.current;
678
+ }, [inputStyle, tag]);
679
+ const stableTextInputProps = useMemo(() => {
680
+ if (!shallowEqual(stableTextInputPropsRef.current, TextInputProps)) {
681
+ console.log(`AutoPositionedPopup TextInputProps deep change detected, updating stable reference - tag: ${tag}`);
682
+ stableTextInputPropsRef.current = TextInputProps;
683
+ }
684
+ return stableTextInputPropsRef.current;
685
+ }, [TextInputProps, tag]);
686
+ // Use useCallback to stabilize onFocus and onBlur callback references
687
+ // Prevent creating new callback functions during parent component redraws to avoid TextInput re-triggering focus
688
+ // Use ref to store latest state values to avoid adding frequently changing values to dependencies
689
+ const stateRef = useRef(state);
690
+ stateRef.current = state;
691
+ const handleTextInputFocus = useCallback(() => {
692
+ const currentTime = Date.now();
693
+ const timeSinceLastFocus = currentTime - lastFocusTimeRef.current;
694
+ console.log('AutoPositionedPopup onFocus=', {
695
+ tag,
696
+ 'state.selectedItem': stateRef.current.selectedItem,
697
+ 'hasTriggeredFocus.current=': hasTriggeredFocus.current,
698
+ 'textInputRef.current=': textInputRef.current,
699
+ 'ref_searchQuery.current=': ref_searchQuery.current,
700
+ 'timeSinceLastFocus': timeSinceLastFocus,
701
+ 'isKeyboardFullyShown': isKeyboardFullyShown,
702
+ 'isFocusEventProcessing': isFocusEventProcessingRef.current
703
+ });
704
+ // Prevent rapid repeated triggers (repeated events within 300ms are ignored)
705
+ if (timeSinceLastFocus < 300) {
706
+ console.log('AutoPositionedPopup onFocus: Skip - event triggered too quickly (< 300ms)');
707
+ return;
708
+ }
709
+ // Skip if keyboard is already open and focus has been handled
710
+ if (isKeyboardFullyShown && hasTriggeredFocus.current) {
711
+ console.log('AutoPositionedPopup onFocus: Skip - keyboard already open and focus handled');
712
+ return;
713
+ }
714
+ // Prevent concurrent processing
715
+ if (isFocusEventProcessingRef.current) {
716
+ console.log('AutoPositionedPopup onFocus: Skip - processing another focus event');
717
+ return;
718
+ }
719
+ isFocusEventProcessingRef.current = true;
720
+ lastFocusTimeRef.current = currentTime;
721
+ if (!hasTriggeredFocus.current) {
722
+ hasTriggeredFocus.current = true;
723
+ ref_isFocus.current = true;
724
+ if (stateRef.current.selectedItem) {
725
+ ref_searchQuery.current = stateRef.current.selectedItem.title;
726
+ }
727
+ if (textInputRef.current && ref_searchQuery.current) {
728
+ textInputRef.current.setNativeProps({
729
+ text: ref_searchQuery.current,
730
+ });
731
+ }
732
+ }
733
+ // Delay resetting processing flag to avoid blocking subsequent legitimate focus events
734
+ setTimeout(() => {
735
+ isFocusEventProcessingRef.current = false;
736
+ }, 100);
737
+ }, [tag, isKeyboardFullyShown]); // Remove state.selectedItem, use stateRef instead
738
+ const handleTextInputBlur = useCallback(() => {
739
+ console.log('AutoPositionedPopup onBlur=', {
740
+ tag,
741
+ 'textInputRef.current': textInputRef.current,
742
+ 'isKeyboardFullyShown': isKeyboardFullyShown,
743
+ 'hasTriggeredFocus.current': hasTriggeredFocus.current
744
+ });
745
+ // If keyboard is still open, this is a false trigger caused by parent component re-render, should not reset
746
+ if (isKeyboardFullyShown && hasTriggeredFocus.current) {
747
+ console.log('AutoPositionedPopup onBlur: Skip - keyboard still open, possibly caused by parent component re-render');
748
+ return;
749
+ }
750
+ // Only reset internal state, do not actively close keyboard
751
+ // Keyboard will close naturally when TextInput loses focus, no need to manually call Keyboard.dismiss()
752
+ hasTriggeredFocus.current = false;
753
+ hasAddedRootView.current = false;
754
+ hasShownRootView.current = false;
755
+ ref_isFocus.current = false;
756
+ setState((prevState) => {
757
+ return Object.assign(Object.assign({}, prevState), { isFocus: false });
758
+ });
759
+ removeRootView(tag, forceRemoveAllRootViewOnItemSelected);
760
+ setSearchQuery('');
761
+ if (textInputRef.current) {
762
+ textInputRef.current.setNativeProps({ text: '' });
763
+ ref_searchQuery.current = '';
764
+ // Remove textInputRef.current.blur() - avoid forcing blur causing keyboard to close
765
+ }
766
+ // Remove Keyboard.dismiss() - let keyboard close naturally to avoid triggering keyboardDidHide event
767
+ }, [tag, isKeyboardFullyShown, forceRemoveAllRootViewOnItemSelected]);
768
+ // Wrap TextInput independently in useMemo to recreate only when key props change
769
+ // This avoids repeated ref callback triggers due to other props changes during parent component redraws
770
+ const memoizedTextInput = useMemo(() => {
771
+ console.log(`AutoPositionedPopup useMemo creating TextInput - tag: ${tag}, isFocus: ${state.isFocus}`);
772
+ if (!useTextInput || !state.isFocus) {
773
+ return null;
774
+ }
775
+ return (<RNTextInput ref={(ref) => {
776
+ // Monitor TextInput mounting and unmounting
777
+ if (ref && !textInputRef.current) {
778
+ console.log(`AutoPositionedPopup TextInput created/mounted - tag: ${tag}, ref:`, ref);
779
+ }
780
+ else if (!ref && textInputRef.current) {
781
+ console.log(`AutoPositionedPopup TextInput unmounted - tag: ${tag}`);
782
+ }
783
+ else if (ref && textInputRef.current && ref !== textInputRef.current) {
784
+ console.log(`AutoPositionedPopup TextInput replaced - tag: ${tag}, oldRef:`, textInputRef.current, 'newRef:', ref);
785
+ }
786
+ textInputRef.current = ref;
787
+ }} key={`textinput-${tag}`} style={[
788
+ styles.inputStyle,
789
+ stableInputStyle,
790
+ ]} textAlign={stableTextInputProps['textAlign'] || 'left'} multiline={stableTextInputProps['multiline'] || false} numberOfLines={stableTextInputProps['numberOfLines'] || 1} onChangeText={(searchQuery) => {
791
+ ref_searchQuery.current = searchQuery;
792
+ console.log('AutoPositionedPopup onChangeText rootViews=', rootViews);
793
+ if (!localSearch) {
794
+ if (debounceTimerRef.current) {
795
+ clearTimeout(debounceTimerRef.current);
796
+ }
797
+ debounceTimerRef.current = setTimeout(() => {
798
+ emitQueryChange(ref_searchQuery.current);
799
+ }, 500);
800
+ }
801
+ else {
802
+ emitQueryChange(ref_searchQuery.current);
803
+ }
804
+ }} placeholderTextColor={theme.colors.placeholderText} placeholder={placeholder} onKeyPress={(e) => {
805
+ if (e.nativeEvent.key === 'Enter') {
806
+ Keyboard.dismiss();
807
+ }
808
+ }} keyboardType={stableTextInputProps['keyboardType'] || 'default'} clearButtonMode="while-editing" returnKeyType={stableTextInputProps['returnKeyType'] || 'done'} maxLength={stableTextInputProps['maxLength'] || 100} accessibilityLabel="selectInput" accessible={true} autoFocus={stableTextInputProps['autoFocus'] || false} autoCorrect={false} underlineColorAndroid="transparent" editable={stableTextInputProps['editable'] || true} secureTextEntry={stableTextInputProps['secureTextEntry'] || false} defaultValue="" caretHidden={false} enablesReturnKeyAutomatically onFocus={handleTextInputFocus} onBlur={handleTextInputBlur} selectTextOnFocus={stableTextInputProps['selectTextOnFocus'] || false} onSubmitEditing={(e) => {
809
+ console.log('AutoPositionedPopup.tsx onSubmitEditing e.nativeEvent.text=', e.nativeEvent.text);
810
+ onSubmitEditing && onSubmitEditing(e);
811
+ }}/>);
812
+ }, [
813
+ tag, // tag 是稳定的
814
+ useTextInput, // useTextInput 是稳定的
815
+ state.isFocus, // isFocus 控制显示/隐藏
816
+ handleTextInputFocus, // useCallback wrapped, reference stable
817
+ handleTextInputBlur, // useCallback wrapped, reference stable
818
+ stableInputStyle, // Use stable inputStyle reference (after deep comparison)
819
+ stableTextInputProps, // Use stable TextInputProps reference (after deep comparison)
820
+ placeholder, // placeholder usually stable
821
+ onSubmitEditing, // onSubmitEditing usually stable
822
+ // No longer use original inputStyle and TextInputProps, use stable references instead
823
+ // Stable references only update when deep comparison detects actual content changes, avoiding frequent TextInput recreation during parent component redraws
824
+ ]);
618
825
  // Render the component following project implementation
619
826
  return useMemo(() => {
620
827
  var _a;
@@ -622,17 +829,20 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
622
829
  return (<CustomRow>
623
830
  <View style={[styles.contain, style]} ref={refAutoPositionedPopup}>
624
831
  {!state.isFocus || !useTextInput ? (<TouchableOpacity style={[styles.AutoPositionedPopupBtn, AutoPositionedPopupBtnStyle]} disabled={AutoPositionedPopupBtnDisabled} onPress={() => {
625
- console.log('AutoPositionedPopup onPress tag=', tag);
626
- console.log('AutoPositionedPopup onPress state.isFocus=', state.isFocus);
627
- console.log('AutoPositionedPopup onPress useTextInput=', useTextInput);
628
- console.log('AutoPositionedPopup onPress hasAddedRootView.current=', hasAddedRootView.current);
629
- console.log('AutoPositionedPopup onPress hasShownRootView.current=', hasShownRootView.current);
630
- console.log('AutoPositionedPopup onPress hasTriggeredFocus.current=', hasTriggeredFocus.current);
631
- console.log('AutoPositionedPopup onPress state.selectedItem=', state.selectedItem);
832
+ console.log('AutoPositionedPopup onPress=', {
833
+ tag,
834
+ 'state.isFocus': state.isFocus,
835
+ useTextInput,
836
+ 'hasAddedRootView.current': hasAddedRootView.current,
837
+ 'hasShownRootView.current': hasShownRootView.current,
838
+ 'hasTriggeredFocus.current': hasTriggeredFocus.current,
839
+ 'state.selectedItem': state.selectedItem
840
+ });
632
841
  setState((prevState) => {
633
842
  return Object.assign(Object.assign({}, prevState), { isFocus: true });
634
843
  });
635
844
  if (!hasAddedRootView.current && useTextInput) {
845
+ // TextInput version: hide first, show after keyboard is fully displayed
636
846
  hasAddedRootView.current = true;
637
847
  hasShownRootView.current = false;
638
848
  addRootView({
@@ -644,10 +854,11 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
644
854
  height: listLayout.height,
645
855
  opacity: 0,
646
856
  },
647
- component: (<AutoPositionedPopupList tag={tag} updateState={updateState} fetchData={fetchData} pageSize={pageSize} renderItem={renderItem} selectedItem={state.selectedItem} localSearch={localSearch}/>),
857
+ component: (<AutoPositionedPopupList tag={tag} updateState={updateState} fetchData={fetchData} pageSize={pageSize} renderItem={renderItem} selectedItem={state.selectedItem} localSearch={localSearch} showListEmptyComponent={showListEmptyComponent} emptyText={emptyText}/>),
648
858
  useModal: false,
649
859
  });
650
860
  }
861
+ console.log('AutoPositionedPopup onPress done');
651
862
  }}>
652
863
  {!btwChildren ? (<Text style={[
653
864
  styles.searchQueryTxt,
@@ -656,87 +867,36 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
656
867
  ]} numberOfLines={1} ellipsizeMode={'tail'}>
657
868
  {((_a = state.selectedItem) === null || _a === void 0 ? void 0 : _a.title) || placeholder}
658
869
  </Text>) : (btwChildren())}
659
- </TouchableOpacity>) : (useTextInput &&
660
- state.isFocus && (<RNTextInput ref={textInputRef} key="fixed-textinput-key" style={[
661
- styles.inputStyle,
662
- inputStyle,
663
- ]} textAlign={TextInputProps['textAlign'] || 'left'} multiline={TextInputProps['multiline'] || false} numberOfLines={TextInputProps['numberOfLines'] || 1} onChangeText={(searchQuery) => {
664
- ref_searchQuery.current = searchQuery;
665
- console.log('AutoPositionedPopup onChangeText rootViews=', rootViews);
666
- if (!localSearch) {
667
- if (debounceTimerRef.current) {
668
- clearTimeout(debounceTimerRef.current);
669
- }
670
- debounceTimerRef.current = setTimeout(() => {
671
- emitQueryChange(ref_searchQuery.current);
672
- }, 500);
673
- }
674
- else {
675
- emitQueryChange(ref_searchQuery.current);
676
- }
677
- }} placeholderTextColor={theme.colors.placeholderText} placeholder={placeholder} onKeyPress={(e) => {
678
- if (e.nativeEvent.key === 'Enter') {
679
- Keyboard.dismiss();
680
- }
681
- }} keyboardType={TextInputProps['keyboardType'] || 'default'} clearButtonMode="while-editing" returnKeyType={TextInputProps['returnKeyType'] || 'done'} maxLength={TextInputProps['maxLength'] || 100} accessibilityLabel="selectInput" accessible={true} autoFocus={TextInputProps['autoFocus'] || false} autoCorrect={false} underlineColorAndroid="transparent" editable={TextInputProps['editable'] || true} secureTextEntry={TextInputProps['secureTextEntry'] || false} defaultValue="" caretHidden={false} enablesReturnKeyAutomatically onFocus={() => {
682
- console.log('AutoPositionedPopup onFocus tag=', tag, ' selectedItem=', state.selectedItem, ' hasTriggeredFocus.current=', hasTriggeredFocus.current, ' textInputRef.current=', textInputRef.current, ' ref_searchQuery.current=', ref_searchQuery.current);
683
- if (!hasTriggeredFocus.current) {
684
- hasTriggeredFocus.current = true;
685
- ref_isFocus.current = true;
686
- if (state.selectedItem) {
687
- ref_searchQuery.current = state.selectedItem.title;
688
- }
689
- if (textInputRef.current && ref_searchQuery.current) {
690
- textInputRef.current.setNativeProps({
691
- text: ref_searchQuery.current,
692
- });
693
- }
694
- }
695
- }} onBlur={() => {
696
- console.log('AutoPositionedPopup onBlur tag=', tag, 'textInputRef.current=', textInputRef.current);
697
- hasTriggeredFocus.current = false;
698
- hasAddedRootView.current = false; // 重置 RootView 狀態
699
- hasShownRootView.current = false;
700
- ref_isFocus.current = false;
701
- setState((prevState) => {
702
- return Object.assign(Object.assign({}, prevState), { isFocus: false });
703
- });
704
- removeRootView(tag, forceRemoveAllRootViewOnItemSelected);
705
- setSearchQuery('');
706
- if (textInputRef.current) {
707
- textInputRef.current.setNativeProps({ text: '' });
708
- ref_searchQuery.current = '';
709
- textInputRef.current.blur();
710
- }
711
- Keyboard.dismiss();
712
- }} selectTextOnFocus={TextInputProps['selectTextOnFocus'] || false} onSubmitEditing={(e) => {
713
- console.log('AutoPositionedPopup.tsx onSubmitEditing e.nativeEvent.text=', e.nativeEvent.text);
714
- onSubmitEditing && onSubmitEditing(e);
715
- }}/>))}
870
+ </TouchableOpacity>) : (memoizedTextInput)}
716
871
  </View>
717
872
  </CustomRow>);
718
- }, [tag,
719
- fetchData,
720
- renderItem,
721
- onItemSelected,
722
- onSubmitEditing,
873
+ }, [
874
+ tag,
875
+ // ✅ CRITICAL FIX: Remove all props that may change frequently or are inline functions
876
+ // Changes to these props should not cause the entire component tree to recreate, especially TextInput
877
+ // fetchData, // ❌ Removed: inline function
878
+ // renderItem, // ❌ Removed: possibly inline function
879
+ // onItemSelected, // ❌ Removed: possibly inline function
880
+ // onSubmitEditing, // ❌ Removed: possibly inline function
723
881
  localSearch,
724
- placeholder,
725
- textAlign,
882
+ // placeholder, // ❌ Removed: may change
883
+ // textAlign, // ❌ Removed: may change
726
884
  pageSize,
727
885
  selectedItem,
728
- CustomRow,
886
+ // CustomRow, // ❌ Removed: inline function, new reference each time
729
887
  useTextInput,
730
- btwChildren,
731
- selectedItem,
732
- keyExtractor,
733
- AutoPositionedPopupBtnStyle,
734
- CustomPopView,
735
- CustomPopViewStyle,
888
+ // btwChildren, // ❌ Removed: inline function
889
+ // keyExtractor, // ❌ Removed: possibly inline function
890
+ // AutoPositionedPopupBtnStyle, // ❌ Removed: possibly inline object
891
+ // CustomPopView, // ❌ Removed: may change
892
+ // CustomPopViewStyle, // ❌ Removed: may change
736
893
  forceRemoveAllRootViewOnItemSelected,
737
- inputStyle,
738
- TextInputProps,
739
- state.isFocus,]);
894
+ state.isFocus,
895
+ showListEmptyComponent,
896
+ emptyText,
897
+ // ✅ Removed most dependencies that may cause re-rendering, keeping only core dependencies that truly affect component structure
898
+ // This prevents TextInput recreation due to inline functions/objects during parent component redraws
899
+ ]);
740
900
  }));
741
901
  export default AutoPositionedPopup;
742
902
  //# sourceMappingURL=AutoPositionedPopup.js.map