react-native-auto-positioned-popup 1.0.12 → 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.
@@ -1 +1 @@
1
- {"version":3,"file":"AutoPositionedPopup.d.ts","sourceRoot":"","sources":["../src/AutoPositionedPopup.tsx"],"names":[],"mappings":"AAAA,OAAO,KAYN,MAAM,OAAO,CAAC;AAcf,OAAO,EAAC,wBAAwB,EAAqB,MAAM,4BAA4B,CAAC;AA4RxF,QAAA,MAAM,mBAAmB,qFA+vBxB,CAAC;AAEF,eAAe,mBAAmB,CAAC"}
1
+ {"version":3,"file":"AutoPositionedPopup.d.ts","sourceRoot":"","sources":["../src/AutoPositionedPopup.tsx"],"names":[],"mappings":"AAAA,OAAO,KAYN,MAAM,OAAO,CAAC;AAcf,OAAO,EAAC,wBAAwB,EAAqB,MAAM,4BAA4B,CAAC;AAuRxF,QAAA,MAAM,mBAAmB,qFA+4BxB,CAAC;AAEF,eAAe,mBAAmB,CAAC"}
@@ -34,9 +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);
39
- console.log('AutoPositionedPopup.tsx ListItem selectedItem=', selectedItem);
37
+ // console.log('AutoPositionedPopup.tsx ListItem=', {index, item, selectedItem});
40
38
  const isSelected = item.id === (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.id);
41
39
  return (<TouchableOpacity key={item.id} style={[
42
40
  styles.commonModalRow,
@@ -79,8 +77,8 @@ const AutoPositionedPopupList = memo(({ tag, updateState, fetchData, keyExtracto
79
77
  };
80
78
  }, []);
81
79
  // useEffect(() => {
82
- // // 監聽 TextInput 事件,收到就刷新列表,不依賴 global searchQuery
83
- // // 將最新的 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
84
82
  // ref_searchQuery.current = searchQuery;
85
83
  // console.log('AutoPositionedPopupList useEffect searchQuery=', searchQuery);
86
84
  // console.log('AutoPositionedPopupList useEffect state.localData=', state.localData);
@@ -109,10 +107,7 @@ const AutoPositionedPopupList = memo(({ tag, updateState, fetchData, keyExtracto
109
107
  updateState(key, value);
110
108
  };
111
109
  const _fetchData = async ({ pageIndex, pageSize: currentPageSize, }) => {
112
- console.log('AutoPositionedPopupList _fetchData pageIndex=', pageIndex, ' pageSize=', currentPageSize);
113
- console.log('AutoPositionedPopupList _fetchData state.localData=', state.localData);
114
- console.log('AutoPositionedPopupList _fetchData ref_searchQuery.current=', ref_searchQuery.current);
115
- 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 });
116
111
  if (localSearch && state.localData.length > 0) {
117
112
  const result = state.localData.filter((item) => {
118
113
  var _a;
@@ -183,7 +178,7 @@ const listLayout = {
183
178
  // Main AutoPositionedPopup component
184
179
  const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
185
180
  console.log('AutoPositionedPopup props=', props);
186
- 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, }) => {
187
182
  const res = {
188
183
  items: [],
189
184
  pageIndex,
@@ -219,6 +214,16 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
219
214
  const keyboardVisibleRef = useRef(false);
220
215
  const refAutoPositionedPopup = useRef(null);
221
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);
222
227
  // Simple keyboard status tracking (alternative to useKeyboardStatus hook)
223
228
  // Legacy state for compatibility
224
229
  const [isVisible, setIsVisible] = useState(false);
@@ -240,8 +245,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
240
245
  useEffect(() => {
241
246
  (async () => {
242
247
  })();
243
- console.log(`AutoPositionedPopup componentDidMount tag=`, tag);
244
- console.log('AutoPositionedPopup componentDidMount CustomPopView=', CustomPopView);
248
+ console.log(`AutoPositionedPopup componentDidMount=`, { tag, CustomPopView });
245
249
  //componentWillUnmount
246
250
  return () => {
247
251
  console.log(`AutoPositionedPopup componentWillUnmount tag=`, tag);
@@ -272,8 +276,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
272
276
  }, [rootViews]);
273
277
  useEffect(() => {
274
278
  var _a, _b;
275
- console.log('AutoPositionedPopup useEffect tag=', tag);
276
- console.log('AutoPositionedPopup useEffect selectedItem=', selectedItem);
279
+ console.log('AutoPositionedPopup useEffect [selectedItem, state.selectedItem, tag]=', { tag, selectedItem, 'state.selectedItem': state.selectedItem });
277
280
  console.log('AutoPositionedPopup useEffect state.selectedItem=', state.selectedItem);
278
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)) {
279
282
  console.log('AutoPositionedPopup useEffect selectedItem!=state.selectedItem');
@@ -284,24 +287,55 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
284
287
  }, [selectedItem, state.selectedItem, tag]);
285
288
  useEffect(() => {
286
289
  var _a, _b, _c;
287
- console.log('AutoPositionedPopup useEffect tag=', tag);
288
- console.log('AutoPositionedPopup useEffect state.isFocus=', state.isFocus);
289
- console.log('AutoPositionedPopup useEffect isKeyboardFullyShown=', isKeyboardFullyShown);
290
- console.log('AutoPositionedPopup useEffect ref_isFocus.current=', ref_isFocus.current);
291
- console.log('AutoPositionedPopup useEffect ref_isKeyboardFullyShown.current=', ref_isKeyboardFullyShown.current);
292
- console.log('AutoPositionedPopup useEffect useTextInput=', useTextInput);
293
- console.log('AutoPositionedPopup useEffect TextInputProps=', TextInputProps);
294
- console.log('AutoPositionedPopup useEffect hasAddedRootView.current=', hasAddedRootView.current);
295
- 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
+ }
296
330
  if (useTextInput) {
297
331
  if (isKeyboardFullyShown && hasAddedRootView.current && !hasShownRootView.current && state.isFocus) {
298
332
  (_a = refAutoPositionedPopup.current) === null || _a === void 0 ? void 0 : _a.measureInWindow((x, y, width, height) => {
299
333
  var _a;
300
- console.log('AutoPositionedPopup measureInWindow x=', x, ' y=', y, ' width=', width, ' height=', height);
334
+ console.log('AutoPositionedPopup useTextInput measureInWindow=', { x, y, width, height });
301
335
  // SIMPLE CENTER-BASED POSITIONING STRATEGY
302
336
  const screenHeight = Dimensions.get('screen').height;
303
337
  const screenCenter = screenHeight / 2;
304
- console.log('AutoPositionedPopup screenHeight=', screenHeight, ' screenCenter=', screenCenter, ' componentY=', y);
338
+ console.log('AutoPositionedPopup useTextInput measureInWindow =', { screenHeight, screenCenter, componentY: y });
305
339
  // Simple rule: if component Y > screen center, show popup above; otherwise show below
306
340
  if (y > screenCenter) {
307
341
  console.log('AutoPositionedPopup with keyboard: showing above (Y > center)');
@@ -311,7 +345,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
311
345
  console.log('AutoPositionedPopup with keyboard: showing below (Y <= center)');
312
346
  ref_listPos.current = { x: x, y: y + height, width: width };
313
347
  }
314
- console.log('AutoPositionedPopup ref_listPos.current=', ref_listPos.current);
348
+ console.log('AutoPositionedPopup useTextInput ref_listPos.current=', ref_listPos.current);
315
349
  setRootViewNativeStyle(tag, {
316
350
  top: (_a = ref_listPos.current) === null || _a === void 0 ? void 0 : _a.y,
317
351
  left: popUpViewStyle === null || popUpViewStyle === void 0 ? void 0 : popUpViewStyle.left,
@@ -322,8 +356,9 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
322
356
  hasShownRootView.current = true;
323
357
  });
324
358
  }
325
- else if (!isKeyboardFullyShown && ref_isFocus.current) {
326
- 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 });
327
362
  removeRootView(tag, forceRemoveAllRootViewOnItemSelected);
328
363
  setState((prevState) => {
329
364
  return Object.assign(Object.assign({}, prevState), { isFocus: false });
@@ -336,10 +371,10 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
336
371
  else {
337
372
  if (state.isFocus) {
338
373
  (_b = refAutoPositionedPopup.current) === null || _b === void 0 ? void 0 : _b.measureInWindow((x, y, width, height) => {
339
- console.log('AutoPositionedPopup measureInWindow x=', x, ' y=', y, ' width=', width, ' height=', height);
374
+ console.log('AutoPositionedPopup !useTextInput measureInWindow=', { x, y, width, height });
340
375
  // INTELLIGENT POSITION CALCULATION - MODIFIED VERSION WITH STATUS BAR SAFETY
341
376
  const calculateOptimalPosition = (componentY, componentHeight, popupHeight) => {
342
- console.log('🔥🔥🔥 NEW CALCULATE OPTIMAL POSITION FUNCTION EXECUTING 🔥🔥🔥');
377
+ console.log('AutoPositionedPopup 🔥🔥🔥 NEW CALCULATE OPTIMAL POSITION FUNCTION EXECUTING 🔥🔥🔥');
343
378
  // Use window height (visible area) instead of screen height (includes status bar)
344
379
  const windowHeight = Dimensions.get('window').height;
345
380
  const visibleAreaCenter = windowHeight / 2;
@@ -356,7 +391,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
356
391
  }
357
392
  };
358
393
  const statusBarHeight = getStatusBarHeight();
359
- console.log('🔥 Cross-platform StatusBar height:', statusBarHeight, 'Platform:', Platform.OS);
394
+ console.log('AutoPositionedPopup 🔥 Cross-platform StatusBar height:', statusBarHeight, 'Platform:', Platform.OS);
360
395
  // Calculate component center point as requested
361
396
  const componentCenterY = componentY + componentHeight / 2;
362
397
  console.log('AutoPositionedPopup positioning data:', {
@@ -404,7 +439,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
404
439
  // Strong platform adjustment - much smaller for Android bottom components
405
440
  const platformMultiplier = Platform.OS === 'ios' ? 1.0 : (isInBottomHalf ? 0.5 : 0.9);
406
441
  const finalSpacing = Math.max(baseSpacing, relativeSpacing) * edgeProximityFactor * platformMultiplier;
407
- console.log('🔥 Advanced spacing calculation:', {
442
+ console.log('AutoPositionedPopup 🔥 Advanced spacing calculation:', {
408
443
  componentCenter,
409
444
  screenCenter,
410
445
  distanceFromCenter,
@@ -440,7 +475,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
440
475
  // Component in bottom half + enough space above = FORCE ABOVE
441
476
  showAbove = true;
442
477
  finalY = componentY - popupHeight + componentHeight / 2;
443
- 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);
444
479
  }
445
480
  else if (!isInBottomHalf && spaceBelow >= popupHeight) {
446
481
  // Component in top half + enough space below = show below
@@ -474,7 +509,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
474
509
  }
475
510
  }
476
511
  // Enhanced boundary check with detailed logging
477
- console.log('🔥 Pre-boundary check:', {
512
+ console.log('AutoPositionedPopup 🔥 Pre-boundary check:', {
478
513
  originalFinalY: finalY,
479
514
  showAbove,
480
515
  statusBarHeight,
@@ -486,21 +521,21 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
486
521
  if (showAbove && finalY < statusBarHeight) {
487
522
  const oldFinalY = finalY;
488
523
  finalY = statusBarHeight;
489
- 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);
490
525
  }
491
526
  if (!showAbove && finalY + popupHeight > windowHeight) {
492
527
  const oldFinalY = finalY;
493
528
  finalY = windowHeight - popupHeight;
494
- 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);
495
530
  }
496
531
  // CRITICAL CHECK: Detect if boundary check is changing display direction
497
532
  if (showAbove && finalY + popupHeight > componentY) {
498
- console.log('🚨 WARNING: Above positioning may overlap with component!');
533
+ console.log('AutoPositionedPopup 🚨 WARNING: Above positioning may overlap with component!');
499
534
  }
500
535
  if (!showAbove && finalY < componentY + componentHeight) {
501
- console.log('🚨 WARNING: Below positioning may overlap with component!');
536
+ console.log('AutoPositionedPopup 🚨 WARNING: Below positioning may overlap with component!');
502
537
  }
503
- console.log('🔥 Post-boundary check final result:', {
538
+ console.log('AutoPositionedPopup 🔥 Post-boundary check final result:', {
504
539
  finalY,
505
540
  showAbove,
506
541
  'popupTop': finalY,
@@ -514,17 +549,15 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
514
549
  const actualPopupHeight = CustomPopView && CustomPopViewStyle && typeof CustomPopViewStyle.height === 'number'
515
550
  ? CustomPopViewStyle.height
516
551
  : listLayout.height;
517
- console.log('🔥 Using actualPopupHeight for calculation:', actualPopupHeight, 'CustomPopView:', !!CustomPopView);
552
+ console.log('AutoPositionedPopup 🔥 Using actualPopupHeight for calculation:', { actualPopupHeight, CustomPopView: !!CustomPopView });
518
553
  const positionResult = calculateOptimalPosition(y, height, actualPopupHeight);
519
554
  console.log('AutoPositionedPopup FINAL position result:', positionResult);
520
555
  ref_listPos.current = { x: x, y: positionResult.finalY, width: width };
521
- console.log('AutoPositionedPopup ref_listPos.current=', ref_listPos.current);
556
+ console.log('AutoPositionedPopup !useTextInput ref_listPos.current=', ref_listPos.current);
522
557
  if (CustomPopView && CustomPopViewStyle) {
523
- console.log('AutoPositionedPopup CustomPopViewStyle=', CustomPopViewStyle);
524
558
  // Position already calculated correctly above, no need to recalculate
525
559
  const PopViewComponent = CustomPopView();
526
- console.log('AutoPositionedPopup addRootView PopViewComponent=', PopViewComponent);
527
- console.log('AutoPositionedPopup addRootView state.selectedItem=', state.selectedItem);
560
+ console.log('AutoPositionedPopup !useTextInput addRootView=', { CustomPopViewStyle, PopViewComponent, 'state.selectedItem': state.selectedItem });
528
561
  addRootView({
529
562
  id: tag,
530
563
  style: !centerDisplay
@@ -546,7 +579,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
546
579
  });
547
580
  }
548
581
  else {
549
- console.log('AutoPositionedPopup addRootView tag=', tag);
582
+ console.log('AutoPositionedPopup !useTextInput addRootView tag=', tag);
550
583
  addRootView({
551
584
  id: tag,
552
585
  style: {
@@ -588,7 +621,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
588
621
  CustomPopView,
589
622
  CustomPopViewStyle,
590
623
  forceRemoveAllRootViewOnItemSelected,
591
- tag,
624
+ tag, TextInputProps,
592
625
  state.selectedItem, showListEmptyComponent
593
626
  ]);
594
627
  // Imperative handle for parent component access
@@ -601,7 +634,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
601
634
  },
602
635
  }), []);
603
636
  const updateState = (key, value) => {
604
- console.log('AutoPositionedPopup updateState key=', key, ' value=', value);
637
+ console.log('AutoPositionedPopup updateState=', { key, value });
605
638
  setState((prevState) => (Object.assign(Object.assign({}, prevState), { [key]: value })));
606
639
  if (key === 'selectedItem' && onItemSelected) {
607
640
  onItemSelected(value);
@@ -616,6 +649,179 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
616
649
  setSearchQuery('');
617
650
  }
618
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
+ ]);
619
825
  // Render the component following project implementation
620
826
  return useMemo(() => {
621
827
  var _a;
@@ -623,17 +829,20 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
623
829
  return (<CustomRow>
624
830
  <View style={[styles.contain, style]} ref={refAutoPositionedPopup}>
625
831
  {!state.isFocus || !useTextInput ? (<TouchableOpacity style={[styles.AutoPositionedPopupBtn, AutoPositionedPopupBtnStyle]} disabled={AutoPositionedPopupBtnDisabled} onPress={() => {
626
- console.log('AutoPositionedPopup onPress tag=', tag);
627
- console.log('AutoPositionedPopup onPress state.isFocus=', state.isFocus);
628
- console.log('AutoPositionedPopup onPress useTextInput=', useTextInput);
629
- console.log('AutoPositionedPopup onPress hasAddedRootView.current=', hasAddedRootView.current);
630
- console.log('AutoPositionedPopup onPress hasShownRootView.current=', hasShownRootView.current);
631
- console.log('AutoPositionedPopup onPress hasTriggeredFocus.current=', hasTriggeredFocus.current);
632
- 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
+ });
633
841
  setState((prevState) => {
634
842
  return Object.assign(Object.assign({}, prevState), { isFocus: true });
635
843
  });
636
844
  if (!hasAddedRootView.current && useTextInput) {
845
+ // TextInput version: hide first, show after keyboard is fully displayed
637
846
  hasAddedRootView.current = true;
638
847
  hasShownRootView.current = false;
639
848
  addRootView({
@@ -649,6 +858,7 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
649
858
  useModal: false,
650
859
  });
651
860
  }
861
+ console.log('AutoPositionedPopup onPress done');
652
862
  }}>
653
863
  {!btwChildren ? (<Text style={[
654
864
  styles.searchQueryTxt,
@@ -657,87 +867,36 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
657
867
  ]} numberOfLines={1} ellipsizeMode={'tail'}>
658
868
  {((_a = state.selectedItem) === null || _a === void 0 ? void 0 : _a.title) || placeholder}
659
869
  </Text>) : (btwChildren())}
660
- </TouchableOpacity>) : (useTextInput &&
661
- state.isFocus && (<RNTextInput ref={textInputRef} key="fixed-textinput-key" style={[
662
- styles.inputStyle,
663
- inputStyle,
664
- ]} textAlign={TextInputProps['textAlign'] || 'left'} multiline={TextInputProps['multiline'] || false} numberOfLines={TextInputProps['numberOfLines'] || 1} onChangeText={(searchQuery) => {
665
- ref_searchQuery.current = searchQuery;
666
- console.log('AutoPositionedPopup onChangeText rootViews=', rootViews);
667
- if (!localSearch) {
668
- if (debounceTimerRef.current) {
669
- clearTimeout(debounceTimerRef.current);
670
- }
671
- debounceTimerRef.current = setTimeout(() => {
672
- emitQueryChange(ref_searchQuery.current);
673
- }, 500);
674
- }
675
- else {
676
- emitQueryChange(ref_searchQuery.current);
677
- }
678
- }} placeholderTextColor={theme.colors.placeholderText} placeholder={placeholder} onKeyPress={(e) => {
679
- if (e.nativeEvent.key === 'Enter') {
680
- Keyboard.dismiss();
681
- }
682
- }} 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={() => {
683
- console.log('AutoPositionedPopup onFocus tag=', tag, ' selectedItem=', state.selectedItem, ' hasTriggeredFocus.current=', hasTriggeredFocus.current, ' textInputRef.current=', textInputRef.current, ' ref_searchQuery.current=', ref_searchQuery.current);
684
- if (!hasTriggeredFocus.current) {
685
- hasTriggeredFocus.current = true;
686
- ref_isFocus.current = true;
687
- if (state.selectedItem) {
688
- ref_searchQuery.current = state.selectedItem.title;
689
- }
690
- if (textInputRef.current && ref_searchQuery.current) {
691
- textInputRef.current.setNativeProps({
692
- text: ref_searchQuery.current,
693
- });
694
- }
695
- }
696
- }} onBlur={() => {
697
- console.log('AutoPositionedPopup onBlur tag=', tag, 'textInputRef.current=', textInputRef.current);
698
- hasTriggeredFocus.current = false;
699
- hasAddedRootView.current = false; // 重置 RootView 狀態
700
- hasShownRootView.current = false;
701
- ref_isFocus.current = false;
702
- setState((prevState) => {
703
- return Object.assign(Object.assign({}, prevState), { isFocus: false });
704
- });
705
- removeRootView(tag, forceRemoveAllRootViewOnItemSelected);
706
- setSearchQuery('');
707
- if (textInputRef.current) {
708
- textInputRef.current.setNativeProps({ text: '' });
709
- ref_searchQuery.current = '';
710
- textInputRef.current.blur();
711
- }
712
- Keyboard.dismiss();
713
- }} selectTextOnFocus={TextInputProps['selectTextOnFocus'] || false} onSubmitEditing={(e) => {
714
- console.log('AutoPositionedPopup.tsx onSubmitEditing e.nativeEvent.text=', e.nativeEvent.text);
715
- onSubmitEditing && onSubmitEditing(e);
716
- }}/>))}
870
+ </TouchableOpacity>) : (memoizedTextInput)}
717
871
  </View>
718
872
  </CustomRow>);
719
- }, [tag,
720
- fetchData,
721
- renderItem,
722
- onItemSelected,
723
- 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
724
881
  localSearch,
725
- placeholder,
726
- textAlign,
882
+ // placeholder, // ❌ Removed: may change
883
+ // textAlign, // ❌ Removed: may change
727
884
  pageSize,
728
885
  selectedItem,
729
- CustomRow,
886
+ // CustomRow, // ❌ Removed: inline function, new reference each time
730
887
  useTextInput,
731
- btwChildren,
732
- selectedItem,
733
- keyExtractor,
734
- AutoPositionedPopupBtnStyle,
735
- CustomPopView,
736
- 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
737
893
  forceRemoveAllRootViewOnItemSelected,
738
- inputStyle,
739
- TextInputProps,
740
- state.isFocus, showListEmptyComponent, emptyText]);
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
+ ]);
741
900
  }));
742
901
  export default AutoPositionedPopup;
743
902
  //# sourceMappingURL=AutoPositionedPopup.js.map