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.
- package/README.md +94 -0
- package/README_zh.md +94 -0
- package/lib/AutoPositionedPopup.d.ts.map +1 -1
- package/lib/AutoPositionedPopup.js +294 -134
- package/lib/AutoPositionedPopup.js.map +1 -1
- package/lib/AutoPositionedPopupProps.d.ts +8 -0
- package/lib/AutoPositionedPopupProps.d.ts.map +1 -1
- package/package.json +10 -10
- package/src/AutoPositionedPopup.tsx +363 -216
- package/src/AutoPositionedPopupProps.ts +15 -7
|
@@ -88,8 +88,7 @@ const ListItem: React.FC<{
|
|
|
88
88
|
rootViewsRef.current = rootViews;
|
|
89
89
|
}, [rootViews]);
|
|
90
90
|
return useMemo(() => {
|
|
91
|
-
// console.log('AutoPositionedPopup.tsx ListItem
|
|
92
|
-
// console.log('AutoPositionedPopup.tsx ListItem item=', item);
|
|
91
|
+
// console.log('AutoPositionedPopup.tsx ListItem=', {index, item, selectedItem});
|
|
93
92
|
const isSelected = item.id === selectedItem?.id;
|
|
94
93
|
return (
|
|
95
94
|
<TouchableOpacity
|
|
@@ -131,6 +130,8 @@ interface AutoPositionedPopupListProps {
|
|
|
131
130
|
selectedItem?: SelectedItem;
|
|
132
131
|
localSearch?: boolean;
|
|
133
132
|
pageSize?: number;
|
|
133
|
+
showListEmptyComponent?: boolean;
|
|
134
|
+
emptyText?: string;
|
|
134
135
|
}
|
|
135
136
|
|
|
136
137
|
const AutoPositionedPopupList: React.FC<AutoPositionedPopupListProps> = memo(
|
|
@@ -142,7 +143,7 @@ const AutoPositionedPopupList: React.FC<AutoPositionedPopupListProps> = memo(
|
|
|
142
143
|
renderItem,
|
|
143
144
|
selectedItem,
|
|
144
145
|
localSearch,
|
|
145
|
-
pageSize,
|
|
146
|
+
pageSize, showListEmptyComponent, emptyText
|
|
146
147
|
}: AutoPositionedPopupListProps): React.JSX.Element => {
|
|
147
148
|
const [state, setState] = useState<{
|
|
148
149
|
selectedItem?: SelectedItem;
|
|
@@ -173,8 +174,8 @@ const AutoPositionedPopupList: React.FC<AutoPositionedPopupListProps> = memo(
|
|
|
173
174
|
};
|
|
174
175
|
}, []);
|
|
175
176
|
// useEffect(() => {
|
|
176
|
-
// //
|
|
177
|
-
// //
|
|
177
|
+
// // Listen to TextInput events, refresh list when received, not dependent on global searchQuery
|
|
178
|
+
// // Sync the latest searchQuery to list-specific ref for _fetchData to use
|
|
178
179
|
// ref_searchQuery.current = searchQuery;
|
|
179
180
|
// console.log('AutoPositionedPopupList useEffect searchQuery=', searchQuery);
|
|
180
181
|
// console.log('AutoPositionedPopupList useEffect state.localData=', state.localData);
|
|
@@ -209,10 +210,7 @@ const AutoPositionedPopupList: React.FC<AutoPositionedPopupListProps> = memo(
|
|
|
209
210
|
pageIndex,
|
|
210
211
|
pageSize: currentPageSize,
|
|
211
212
|
}: FetchDataParams): Promise<ListData | null> => {
|
|
212
|
-
console.log('AutoPositionedPopupList _fetchData
|
|
213
|
-
console.log('AutoPositionedPopupList _fetchData state.localData=', state.localData);
|
|
214
|
-
console.log('AutoPositionedPopupList _fetchData ref_searchQuery.current=', ref_searchQuery.current);
|
|
215
|
-
console.log('AutoPositionedPopupList _fetchData localSearch=', localSearch);
|
|
213
|
+
console.log('AutoPositionedPopupList _fetchData=', {pageIndex, pageSize: currentPageSize, 'state.localData': state.localData, 'ref_searchQuery.current': ref_searchQuery.current, localSearch});
|
|
216
214
|
if (localSearch && state.localData.length > 0) {
|
|
217
215
|
const result: SelectedItem[] = state.localData.filter((item: SelectedItem) => {
|
|
218
216
|
return item.title?.toLowerCase().includes(ref_searchQuery.current.toLowerCase());
|
|
@@ -273,7 +271,8 @@ const AutoPositionedPopupList: React.FC<AutoPositionedPopupListProps> = memo(
|
|
|
273
271
|
keyboardShouldPersistTaps={'always'}
|
|
274
272
|
fetchData={_fetchData}
|
|
275
273
|
renderItem={renderItem ? ({item, index}) => renderItem({item: item as SelectedItem, index}) : ({item, index}) => _renderItem({item: item as SelectedItem, index})}
|
|
276
|
-
showListEmptyComponent={
|
|
274
|
+
showListEmptyComponent={showListEmptyComponent}
|
|
275
|
+
emptyText={emptyText}
|
|
277
276
|
/>
|
|
278
277
|
</View>
|
|
279
278
|
);
|
|
@@ -287,7 +286,7 @@ const AutoPositionedPopupList: React.FC<AutoPositionedPopupListProps> = memo(
|
|
|
287
286
|
searchQuery,
|
|
288
287
|
localSearch,
|
|
289
288
|
pageSize,
|
|
290
|
-
rootViewsRef,
|
|
289
|
+
rootViewsRef, showListEmptyComponent, emptyText
|
|
291
290
|
]);
|
|
292
291
|
}
|
|
293
292
|
);
|
|
@@ -314,7 +313,7 @@ const AutoPositionedPopup = memo(
|
|
|
314
313
|
AutoPositionedPopupBtnStyle,
|
|
315
314
|
placeholder = 'Please Select',
|
|
316
315
|
onSubmitEditing,
|
|
317
|
-
TextInputProps = {},
|
|
316
|
+
TextInputProps = {autoFocus: true},
|
|
318
317
|
inputStyle,
|
|
319
318
|
labelStyle,
|
|
320
319
|
popUpViewStyle = {left: '5%', width: '90%'},
|
|
@@ -356,7 +355,7 @@ const AutoPositionedPopup = memo(
|
|
|
356
355
|
centerDisplay = false,
|
|
357
356
|
selectedItemBackgroundColor = 'rgba(116, 116, 128, 0.08)',
|
|
358
357
|
textAlign = 'right',
|
|
359
|
-
CustomPopView = undefined, CustomPopViewStyle
|
|
358
|
+
CustomPopView = undefined, CustomPopViewStyle, showListEmptyComponent = true, emptyText = ''
|
|
360
359
|
} = props;
|
|
361
360
|
// State management similar to project implementation
|
|
362
361
|
const [state, setState] = useState<StateProps>({
|
|
@@ -377,6 +376,20 @@ const AutoPositionedPopup = memo(
|
|
|
377
376
|
const keyboardVisibleRef = useRef(false);
|
|
378
377
|
const refAutoPositionedPopup = useRef<View>(null);
|
|
379
378
|
const ref_searchQuery = useRef<string>('');
|
|
379
|
+
// Add ref to track previous keyboard state to avoid false triggers during parent component re-renders
|
|
380
|
+
const prevIsKeyboardFullyShownRef = useRef<boolean>(false);
|
|
381
|
+
const prevPropsRef = useRef<{
|
|
382
|
+
CustomPopView?: any;
|
|
383
|
+
CustomPopViewStyle?: any;
|
|
384
|
+
TextInputProps?: any;
|
|
385
|
+
}>({});
|
|
386
|
+
// Add ref to prevent onFocus/onBlur loop triggers during parent component re-renders
|
|
387
|
+
const lastFocusTimeRef = useRef<number>(0);
|
|
388
|
+
const isFocusEventProcessingRef = useRef<boolean>(false);
|
|
389
|
+
// Add ref to stabilize TextInput props reference
|
|
390
|
+
// Only update when deep comparison detects real changes to avoid TextInput recreation due to reference changes during parent component redraws
|
|
391
|
+
const stableInputStyleRef = useRef<any>(inputStyle);
|
|
392
|
+
const stableTextInputPropsRef = useRef<any>(TextInputProps);
|
|
380
393
|
// Simple keyboard status tracking (alternative to useKeyboardStatus hook)
|
|
381
394
|
// Legacy state for compatibility
|
|
382
395
|
const [isVisible, setIsVisible] = useState(false);
|
|
@@ -402,8 +415,7 @@ const AutoPositionedPopup = memo(
|
|
|
402
415
|
useEffect(() => {
|
|
403
416
|
(async () => {
|
|
404
417
|
})();
|
|
405
|
-
console.log(`AutoPositionedPopup componentDidMount tag
|
|
406
|
-
console.log('AutoPositionedPopup componentDidMount CustomPopView=', CustomPopView);
|
|
418
|
+
console.log(`AutoPositionedPopup componentDidMount=`, {tag, CustomPopView});
|
|
407
419
|
//componentWillUnmount
|
|
408
420
|
return () => {
|
|
409
421
|
console.log(`AutoPositionedPopup componentWillUnmount tag=`, tag);
|
|
@@ -436,8 +448,7 @@ const AutoPositionedPopup = memo(
|
|
|
436
448
|
}
|
|
437
449
|
}, [rootViews]);
|
|
438
450
|
useEffect(() => {
|
|
439
|
-
console.log('AutoPositionedPopup useEffect tag=', tag);
|
|
440
|
-
console.log('AutoPositionedPopup useEffect selectedItem=', selectedItem);
|
|
451
|
+
console.log('AutoPositionedPopup useEffect [selectedItem, state.selectedItem, tag]=', {tag, selectedItem, 'state.selectedItem': state.selectedItem});
|
|
441
452
|
console.log('AutoPositionedPopup useEffect state.selectedItem=', state.selectedItem);
|
|
442
453
|
if (state.selectedItem?.id !== selectedItem?.id || state.selectedItem?.title !== selectedItem?.title) {
|
|
443
454
|
console.log('AutoPositionedPopup useEffect selectedItem!=state.selectedItem');
|
|
@@ -450,26 +461,55 @@ const AutoPositionedPopup = memo(
|
|
|
450
461
|
}
|
|
451
462
|
}, [selectedItem, state.selectedItem, tag]);
|
|
452
463
|
useEffect(() => {
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
464
|
+
// Detect if keyboard state has actually changed to avoid false triggers during parent component re-renders
|
|
465
|
+
const keyboardStateChanged = prevIsKeyboardFullyShownRef.current !== isKeyboardFullyShown;
|
|
466
|
+
const propsChanged =
|
|
467
|
+
prevPropsRef.current.CustomPopView !== CustomPopView ||
|
|
468
|
+
prevPropsRef.current.CustomPopViewStyle !== CustomPopViewStyle ||
|
|
469
|
+
prevPropsRef.current.TextInputProps !== TextInputProps;
|
|
470
|
+
console.log('AutoPositionedPopup useEffect [isKeyboardFullyShown,\n' +
|
|
471
|
+
' state.isFocus,\n' +
|
|
472
|
+
' useTextInput,\n' +
|
|
473
|
+
' CustomPopView,\n' +
|
|
474
|
+
' CustomPopViewStyle,\n' +
|
|
475
|
+
' forceRemoveAllRootViewOnItemSelected,\n' +
|
|
476
|
+
' tag, TextInputProps,\n' +
|
|
477
|
+
' state.selectedItem, showListEmptyComponent\n' +
|
|
478
|
+
' ]=', {
|
|
479
|
+
tag,
|
|
480
|
+
'state.isFocus': state.isFocus,
|
|
481
|
+
isKeyboardFullyShown,
|
|
482
|
+
'ref_isFocus.current': ref_isFocus.current,
|
|
483
|
+
'ref_isKeyboardFullyShown.current': ref_isKeyboardFullyShown.current,
|
|
484
|
+
useTextInput, TextInputProps,
|
|
485
|
+
'hasAddedRootView.current': hasAddedRootView.current,
|
|
486
|
+
'hasShownRootView.current': hasShownRootView.current,
|
|
487
|
+
'keyboardStateChanged': keyboardStateChanged,
|
|
488
|
+
'propsChanged': propsChanged
|
|
489
|
+
});
|
|
490
|
+
// Update ref to record current state
|
|
491
|
+
prevIsKeyboardFullyShownRef.current = isKeyboardFullyShown;
|
|
492
|
+
prevPropsRef.current = {
|
|
493
|
+
CustomPopView,
|
|
494
|
+
CustomPopViewStyle,
|
|
495
|
+
TextInputProps
|
|
496
|
+
};
|
|
497
|
+
// Only execute logic when keyboard state actually changes or user actively operates
|
|
498
|
+
if (!keyboardStateChanged && hasAddedRootView.current) {
|
|
499
|
+
console.log('AutoPositionedPopup: Skip execution - parent component re-rendered but keyboard state unchanged');
|
|
500
|
+
// if (!ref_isFocus.current) {
|
|
501
|
+
// textInputRef.current?.focus()
|
|
502
|
+
// }
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
465
505
|
if (useTextInput) {
|
|
466
506
|
if (isKeyboardFullyShown && hasAddedRootView.current && !hasShownRootView.current && state.isFocus) {
|
|
467
507
|
refAutoPositionedPopup.current?.measureInWindow((x: number, y: number, width: number, height: number) => {
|
|
468
|
-
console.log('AutoPositionedPopup measureInWindow
|
|
508
|
+
console.log('AutoPositionedPopup useTextInput measureInWindow=', {x, y, width, height});
|
|
469
509
|
// SIMPLE CENTER-BASED POSITIONING STRATEGY
|
|
470
510
|
const screenHeight = Dimensions.get('screen').height;
|
|
471
511
|
const screenCenter = screenHeight / 2;
|
|
472
|
-
console.log('AutoPositionedPopup
|
|
512
|
+
console.log('AutoPositionedPopup useTextInput measureInWindow =', {screenHeight, screenCenter, componentY: y});
|
|
473
513
|
|
|
474
514
|
// Simple rule: if component Y > screen center, show popup above; otherwise show below
|
|
475
515
|
if (y > screenCenter) {
|
|
@@ -479,7 +519,7 @@ const AutoPositionedPopup = memo(
|
|
|
479
519
|
console.log('AutoPositionedPopup with keyboard: showing below (Y <= center)');
|
|
480
520
|
ref_listPos.current = {x: x, y: y + height, width: width};
|
|
481
521
|
}
|
|
482
|
-
console.log('AutoPositionedPopup ref_listPos.current=', ref_listPos.current);
|
|
522
|
+
console.log('AutoPositionedPopup useTextInput ref_listPos.current=', ref_listPos.current);
|
|
483
523
|
setRootViewNativeStyle(tag, {
|
|
484
524
|
top: ref_listPos.current?.y,
|
|
485
525
|
left: popUpViewStyle?.left,
|
|
@@ -489,12 +529,11 @@ const AutoPositionedPopup = memo(
|
|
|
489
529
|
});
|
|
490
530
|
hasShownRootView.current = true;
|
|
491
531
|
});
|
|
492
|
-
} else if (!isKeyboardFullyShown && ref_isFocus.current) {
|
|
532
|
+
} else if (!isKeyboardFullyShown && ref_isFocus.current && keyboardStateChanged) {
|
|
533
|
+
// Only execute close logic when keyboard state actually changes from true to false
|
|
493
534
|
console.log(
|
|
494
|
-
'AutoPositionedPopup isKeyboardFullyShown useEffect removeRootView
|
|
495
|
-
tag,
|
|
496
|
-
' forceRemoveAllRootViewOnItemSelected=',
|
|
497
|
-
forceRemoveAllRootViewOnItemSelected
|
|
535
|
+
'AutoPositionedPopup isKeyboardFullyShown useEffect removeRootView (keyboard state changed)=',
|
|
536
|
+
{tag, forceRemoveAllRootViewOnItemSelected, keyboardStateChanged}
|
|
498
537
|
);
|
|
499
538
|
removeRootView(tag, forceRemoveAllRootViewOnItemSelected);
|
|
500
539
|
setState((prevState) => {
|
|
@@ -510,11 +549,10 @@ const AutoPositionedPopup = memo(
|
|
|
510
549
|
} else {
|
|
511
550
|
if (state.isFocus) {
|
|
512
551
|
refAutoPositionedPopup.current?.measureInWindow((x: number, y: number, width: number, height: number) => {
|
|
513
|
-
console.log('AutoPositionedPopup measureInWindow
|
|
514
|
-
|
|
552
|
+
console.log('AutoPositionedPopup !useTextInput measureInWindow=', {x, y, width, height});
|
|
515
553
|
// INTELLIGENT POSITION CALCULATION - MODIFIED VERSION WITH STATUS BAR SAFETY
|
|
516
554
|
const calculateOptimalPosition = (componentY: number, componentHeight: number, popupHeight: number) => {
|
|
517
|
-
console.log('🔥🔥🔥 NEW CALCULATE OPTIMAL POSITION FUNCTION EXECUTING 🔥🔥🔥');
|
|
555
|
+
console.log('AutoPositionedPopup 🔥🔥🔥 NEW CALCULATE OPTIMAL POSITION FUNCTION EXECUTING 🔥🔥🔥');
|
|
518
556
|
|
|
519
557
|
// Use window height (visible area) instead of screen height (includes status bar)
|
|
520
558
|
const windowHeight = Dimensions.get('window').height;
|
|
@@ -532,7 +570,7 @@ const AutoPositionedPopup = memo(
|
|
|
532
570
|
}
|
|
533
571
|
};
|
|
534
572
|
const statusBarHeight = getStatusBarHeight();
|
|
535
|
-
console.log('🔥 Cross-platform StatusBar height:', statusBarHeight, 'Platform:', Platform.OS);
|
|
573
|
+
console.log('AutoPositionedPopup 🔥 Cross-platform StatusBar height:', statusBarHeight, 'Platform:', Platform.OS);
|
|
536
574
|
|
|
537
575
|
// Calculate component center point as requested
|
|
538
576
|
const componentCenterY = componentY + componentHeight / 2;
|
|
@@ -593,7 +631,7 @@ const AutoPositionedPopup = memo(
|
|
|
593
631
|
|
|
594
632
|
const finalSpacing = Math.max(baseSpacing, relativeSpacing) * edgeProximityFactor * platformMultiplier;
|
|
595
633
|
|
|
596
|
-
console.log('🔥 Advanced spacing calculation:', {
|
|
634
|
+
console.log('AutoPositionedPopup 🔥 Advanced spacing calculation:', {
|
|
597
635
|
componentCenter,
|
|
598
636
|
screenCenter,
|
|
599
637
|
distanceFromCenter,
|
|
@@ -630,41 +668,41 @@ const AutoPositionedPopup = memo(
|
|
|
630
668
|
// 'usableSpaceAbove >= needed': usableSpaceAbove >= popupHeight + POPUP_SPACING
|
|
631
669
|
// });
|
|
632
670
|
|
|
633
|
-
if (isInBottomHalf && usableSpaceAbove >= popupHeight
|
|
671
|
+
if (isInBottomHalf && usableSpaceAbove >= popupHeight) {
|
|
634
672
|
// Component in bottom half + enough space above = FORCE ABOVE
|
|
635
673
|
showAbove = true;
|
|
636
|
-
finalY = componentY - popupHeight +componentHeight/2;
|
|
637
|
-
console.log('🔥 AutoPositionedPopup: FORCE ABOVE - bottom half component with enough space, finalY=', finalY);
|
|
638
|
-
} else if (!isInBottomHalf && spaceBelow >= popupHeight
|
|
674
|
+
finalY = componentY - popupHeight + componentHeight / 2;
|
|
675
|
+
console.log('AutoPositionedPopup 🔥 AutoPositionedPopup: FORCE ABOVE - bottom half component with enough space, finalY=', finalY);
|
|
676
|
+
} else if (!isInBottomHalf && spaceBelow >= popupHeight) {
|
|
639
677
|
// Component in top half + enough space below = show below
|
|
640
678
|
showAbove = false;
|
|
641
|
-
finalY = componentY + componentHeight*2;
|
|
679
|
+
finalY = componentY + componentHeight * 2;
|
|
642
680
|
console.log('🔥 AutoPositionedPopup: Showing below - top half component with enough space, finalY=', finalY);
|
|
643
|
-
} else if (usableSpaceAbove >= popupHeight
|
|
681
|
+
} else if (usableSpaceAbove >= popupHeight) {
|
|
644
682
|
// Fallback: enough space above
|
|
645
683
|
showAbove = true;
|
|
646
|
-
finalY = componentY - popupHeight
|
|
684
|
+
finalY = componentY - popupHeight;
|
|
647
685
|
console.log('🔥 AutoPositionedPopup: Showing above - enough space available (fallback), finalY=', finalY);
|
|
648
|
-
} else if (spaceBelow >= popupHeight
|
|
686
|
+
} else if (spaceBelow >= popupHeight) {
|
|
649
687
|
// Fallback: enough space below
|
|
650
688
|
showAbove = false;
|
|
651
|
-
finalY = componentY + componentHeight
|
|
689
|
+
finalY = componentY + componentHeight;
|
|
652
690
|
console.log('🔥 AutoPositionedPopup: Showing below - enough space available (fallback), finalY=', finalY);
|
|
653
691
|
} else {
|
|
654
692
|
// Emergency fallback: choose larger space
|
|
655
693
|
if (usableSpaceAbove >= spaceBelow) {
|
|
656
694
|
showAbove = true;
|
|
657
|
-
finalY = Math.max(statusBarHeight, componentY - popupHeight
|
|
695
|
+
finalY = Math.max(statusBarHeight, componentY - popupHeight);
|
|
658
696
|
console.log('🔥 AutoPositionedPopup: Emergency above - larger space, finalY=', finalY);
|
|
659
697
|
} else {
|
|
660
698
|
showAbove = false;
|
|
661
|
-
finalY = componentY + componentHeight
|
|
699
|
+
finalY = componentY + componentHeight;
|
|
662
700
|
console.log('🔥 AutoPositionedPopup: Emergency below - larger space, finalY=', finalY);
|
|
663
701
|
}
|
|
664
702
|
}
|
|
665
703
|
|
|
666
704
|
// Enhanced boundary check with detailed logging
|
|
667
|
-
console.log('🔥 Pre-boundary check:', {
|
|
705
|
+
console.log('AutoPositionedPopup 🔥 Pre-boundary check:', {
|
|
668
706
|
originalFinalY: finalY,
|
|
669
707
|
showAbove,
|
|
670
708
|
statusBarHeight,
|
|
@@ -677,25 +715,25 @@ const AutoPositionedPopup = memo(
|
|
|
677
715
|
if (showAbove && finalY < statusBarHeight) {
|
|
678
716
|
const oldFinalY = finalY;
|
|
679
717
|
finalY = statusBarHeight;
|
|
680
|
-
console.log('🔥 BOUNDARY
|
|
718
|
+
console.log('AutoPositionedPopup 🔥 BOUNDARY : Above display adjusted for status bar:', oldFinalY, '->', finalY);
|
|
681
719
|
}
|
|
682
720
|
|
|
683
721
|
if (!showAbove && finalY + popupHeight > windowHeight) {
|
|
684
722
|
const oldFinalY = finalY;
|
|
685
723
|
finalY = windowHeight - popupHeight;
|
|
686
|
-
console.log('🔥 BOUNDARY
|
|
724
|
+
console.log('AutoPositionedPopup 🔥 BOUNDARY : Below display adjusted to fit window:', oldFinalY, '->', finalY);
|
|
687
725
|
}
|
|
688
726
|
|
|
689
727
|
// CRITICAL CHECK: Detect if boundary check is changing display direction
|
|
690
|
-
if (showAbove && finalY + popupHeight > componentY
|
|
691
|
-
console.log('🚨 WARNING: Above positioning may overlap with component!');
|
|
728
|
+
if (showAbove && finalY + popupHeight > componentY) {
|
|
729
|
+
console.log('AutoPositionedPopup 🚨 WARNING: Above positioning may overlap with component!');
|
|
692
730
|
}
|
|
693
731
|
|
|
694
|
-
if (!showAbove && finalY < componentY + componentHeight
|
|
695
|
-
console.log('🚨 WARNING: Below positioning may overlap with component!');
|
|
732
|
+
if (!showAbove && finalY < componentY + componentHeight) {
|
|
733
|
+
console.log('AutoPositionedPopup 🚨 WARNING: Below positioning may overlap with component!');
|
|
696
734
|
}
|
|
697
735
|
|
|
698
|
-
console.log('🔥 Post-boundary check final result:', {
|
|
736
|
+
console.log('AutoPositionedPopup 🔥 Post-boundary check final result:', {
|
|
699
737
|
finalY,
|
|
700
738
|
showAbove,
|
|
701
739
|
'popupTop': finalY,
|
|
@@ -711,21 +749,15 @@ const AutoPositionedPopup = memo(
|
|
|
711
749
|
const actualPopupHeight = CustomPopView && CustomPopViewStyle && typeof CustomPopViewStyle.height === 'number'
|
|
712
750
|
? CustomPopViewStyle.height
|
|
713
751
|
: listLayout.height;
|
|
714
|
-
|
|
715
|
-
console.log('🔥 Using actualPopupHeight for calculation:', actualPopupHeight, 'CustomPopView:', !!CustomPopView);
|
|
716
|
-
|
|
752
|
+
console.log('AutoPositionedPopup 🔥 Using actualPopupHeight for calculation:', {actualPopupHeight, CustomPopView: !!CustomPopView});
|
|
717
753
|
const positionResult = calculateOptimalPosition(y, height, actualPopupHeight);
|
|
718
754
|
console.log('AutoPositionedPopup FINAL position result:', positionResult);
|
|
719
|
-
|
|
720
755
|
ref_listPos.current = {x: x, y: positionResult.finalY, width: width};
|
|
721
|
-
console.log('AutoPositionedPopup ref_listPos.current=', ref_listPos.current);
|
|
722
|
-
|
|
756
|
+
console.log('AutoPositionedPopup !useTextInput ref_listPos.current=', ref_listPos.current);
|
|
723
757
|
if (CustomPopView && CustomPopViewStyle) {
|
|
724
|
-
console.log('AutoPositionedPopup CustomPopViewStyle=', CustomPopViewStyle);
|
|
725
758
|
// Position already calculated correctly above, no need to recalculate
|
|
726
759
|
const PopViewComponent = CustomPopView();
|
|
727
|
-
console.log('AutoPositionedPopup addRootView
|
|
728
|
-
console.log('AutoPositionedPopup addRootView state.selectedItem=', state.selectedItem);
|
|
760
|
+
console.log('AutoPositionedPopup !useTextInput addRootView=', {CustomPopViewStyle, PopViewComponent, 'state.selectedItem': state.selectedItem});
|
|
729
761
|
addRootView({
|
|
730
762
|
id: tag,
|
|
731
763
|
style: !centerDisplay
|
|
@@ -757,7 +789,7 @@ const AutoPositionedPopup = memo(
|
|
|
757
789
|
centerDisplay,
|
|
758
790
|
});
|
|
759
791
|
} else {
|
|
760
|
-
console.log('AutoPositionedPopup addRootView tag=', tag);
|
|
792
|
+
console.log('AutoPositionedPopup !useTextInput addRootView tag=', tag);
|
|
761
793
|
addRootView({
|
|
762
794
|
id: tag,
|
|
763
795
|
style: {
|
|
@@ -776,6 +808,8 @@ const AutoPositionedPopup = memo(
|
|
|
776
808
|
renderItem={renderItem}
|
|
777
809
|
selectedItem={state.selectedItem}
|
|
778
810
|
localSearch={localSearch}
|
|
811
|
+
showListEmptyComponent={showListEmptyComponent}
|
|
812
|
+
emptyText={emptyText}
|
|
779
813
|
/>
|
|
780
814
|
),
|
|
781
815
|
useModal: true,
|
|
@@ -811,8 +845,8 @@ const AutoPositionedPopup = memo(
|
|
|
811
845
|
CustomPopView,
|
|
812
846
|
CustomPopViewStyle,
|
|
813
847
|
forceRemoveAllRootViewOnItemSelected,
|
|
814
|
-
tag,
|
|
815
|
-
state.selectedItem,
|
|
848
|
+
tag, TextInputProps,
|
|
849
|
+
state.selectedItem, showListEmptyComponent
|
|
816
850
|
]);
|
|
817
851
|
// Imperative handle for parent component access
|
|
818
852
|
useImperativeHandle(
|
|
@@ -831,7 +865,7 @@ const AutoPositionedPopup = memo(
|
|
|
831
865
|
[]
|
|
832
866
|
);
|
|
833
867
|
const updateState = (key: string, value: SelectedItem) => {
|
|
834
|
-
console.log('AutoPositionedPopup updateState
|
|
868
|
+
console.log('AutoPositionedPopup updateState=', {key, value});
|
|
835
869
|
setState((prevState) => ({
|
|
836
870
|
...prevState,
|
|
837
871
|
[key]: value,
|
|
@@ -852,6 +886,225 @@ const AutoPositionedPopup = memo(
|
|
|
852
886
|
setSearchQuery('');
|
|
853
887
|
}
|
|
854
888
|
};
|
|
889
|
+
|
|
890
|
+
// Simple deep comparison function (for style objects only)
|
|
891
|
+
const shallowEqual = (obj1: any, obj2: any): boolean => {
|
|
892
|
+
if (obj1 === obj2) return true;
|
|
893
|
+
if (!obj1 || !obj2) return false;
|
|
894
|
+
if (typeof obj1 !== 'object' || typeof obj2 !== 'object') return false;
|
|
895
|
+
|
|
896
|
+
const keys1 = Object.keys(obj1);
|
|
897
|
+
const keys2 = Object.keys(obj2);
|
|
898
|
+
if (keys1.length !== keys2.length) return false;
|
|
899
|
+
|
|
900
|
+
for (const key of keys1) {
|
|
901
|
+
if (obj1[key] !== obj2[key]) return false;
|
|
902
|
+
}
|
|
903
|
+
return true;
|
|
904
|
+
};
|
|
905
|
+
|
|
906
|
+
// Use useMemo to create stable props reference
|
|
907
|
+
// Only update when deep comparison detects real changes to avoid TextInput recreation due to reference changes during parent component redraws
|
|
908
|
+
const stableInputStyle = useMemo(() => {
|
|
909
|
+
if (!shallowEqual(stableInputStyleRef.current, inputStyle)) {
|
|
910
|
+
console.log(`AutoPositionedPopup inputStyle deep change detected, updating stable reference - tag: ${tag}`);
|
|
911
|
+
stableInputStyleRef.current = inputStyle;
|
|
912
|
+
}
|
|
913
|
+
return stableInputStyleRef.current;
|
|
914
|
+
}, [inputStyle, tag]);
|
|
915
|
+
|
|
916
|
+
const stableTextInputProps = useMemo(() => {
|
|
917
|
+
if (!shallowEqual(stableTextInputPropsRef.current, TextInputProps)) {
|
|
918
|
+
console.log(`AutoPositionedPopup TextInputProps deep change detected, updating stable reference - tag: ${tag}`);
|
|
919
|
+
stableTextInputPropsRef.current = TextInputProps;
|
|
920
|
+
}
|
|
921
|
+
return stableTextInputPropsRef.current;
|
|
922
|
+
}, [TextInputProps, tag]);
|
|
923
|
+
|
|
924
|
+
// Use useCallback to stabilize onFocus and onBlur callback references
|
|
925
|
+
// Prevent creating new callback functions during parent component redraws to avoid TextInput re-triggering focus
|
|
926
|
+
// Use ref to store latest state values to avoid adding frequently changing values to dependencies
|
|
927
|
+
const stateRef = useRef(state);
|
|
928
|
+
stateRef.current = state;
|
|
929
|
+
|
|
930
|
+
const handleTextInputFocus = useCallback(() => {
|
|
931
|
+
const currentTime = Date.now();
|
|
932
|
+
const timeSinceLastFocus = currentTime - lastFocusTimeRef.current;
|
|
933
|
+
console.log(
|
|
934
|
+
'AutoPositionedPopup onFocus=',
|
|
935
|
+
{
|
|
936
|
+
tag,
|
|
937
|
+
'state.selectedItem': stateRef.current.selectedItem,
|
|
938
|
+
'hasTriggeredFocus.current=': hasTriggeredFocus.current,
|
|
939
|
+
'textInputRef.current=': textInputRef.current,
|
|
940
|
+
'ref_searchQuery.current=': ref_searchQuery.current,
|
|
941
|
+
'timeSinceLastFocus': timeSinceLastFocus,
|
|
942
|
+
'isKeyboardFullyShown': isKeyboardFullyShown,
|
|
943
|
+
'isFocusEventProcessing': isFocusEventProcessingRef.current
|
|
944
|
+
}
|
|
945
|
+
);
|
|
946
|
+
// Prevent rapid repeated triggers (repeated events within 300ms are ignored)
|
|
947
|
+
if (timeSinceLastFocus < 300) {
|
|
948
|
+
console.log('AutoPositionedPopup onFocus: Skip - event triggered too quickly (< 300ms)');
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
951
|
+
// Skip if keyboard is already open and focus has been handled
|
|
952
|
+
if (isKeyboardFullyShown && hasTriggeredFocus.current) {
|
|
953
|
+
console.log('AutoPositionedPopup onFocus: Skip - keyboard already open and focus handled');
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
// Prevent concurrent processing
|
|
957
|
+
if (isFocusEventProcessingRef.current) {
|
|
958
|
+
console.log('AutoPositionedPopup onFocus: Skip - processing another focus event');
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
isFocusEventProcessingRef.current = true;
|
|
962
|
+
lastFocusTimeRef.current = currentTime;
|
|
963
|
+
if (!hasTriggeredFocus.current) {
|
|
964
|
+
hasTriggeredFocus.current = true;
|
|
965
|
+
ref_isFocus.current = true;
|
|
966
|
+
if (stateRef.current.selectedItem) {
|
|
967
|
+
ref_searchQuery.current = stateRef.current.selectedItem.title;
|
|
968
|
+
}
|
|
969
|
+
if (textInputRef.current && ref_searchQuery.current) {
|
|
970
|
+
textInputRef.current.setNativeProps({
|
|
971
|
+
text: ref_searchQuery.current,
|
|
972
|
+
});
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
// Delay resetting processing flag to avoid blocking subsequent legitimate focus events
|
|
976
|
+
setTimeout(() => {
|
|
977
|
+
isFocusEventProcessingRef.current = false;
|
|
978
|
+
}, 100);
|
|
979
|
+
}, [tag, isKeyboardFullyShown]); // Remove state.selectedItem, use stateRef instead
|
|
980
|
+
|
|
981
|
+
const handleTextInputBlur = useCallback(() => {
|
|
982
|
+
console.log(
|
|
983
|
+
'AutoPositionedPopup onBlur=',
|
|
984
|
+
{
|
|
985
|
+
tag,
|
|
986
|
+
'textInputRef.current': textInputRef.current,
|
|
987
|
+
'isKeyboardFullyShown': isKeyboardFullyShown,
|
|
988
|
+
'hasTriggeredFocus.current': hasTriggeredFocus.current
|
|
989
|
+
}
|
|
990
|
+
);
|
|
991
|
+
// If keyboard is still open, this is a false trigger caused by parent component re-render, should not reset
|
|
992
|
+
if (isKeyboardFullyShown && hasTriggeredFocus.current) {
|
|
993
|
+
console.log('AutoPositionedPopup onBlur: Skip - keyboard still open, possibly caused by parent component re-render');
|
|
994
|
+
return;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
// Only reset internal state, do not actively close keyboard
|
|
998
|
+
// Keyboard will close naturally when TextInput loses focus, no need to manually call Keyboard.dismiss()
|
|
999
|
+
hasTriggeredFocus.current = false;
|
|
1000
|
+
hasAddedRootView.current = false;
|
|
1001
|
+
hasShownRootView.current = false;
|
|
1002
|
+
ref_isFocus.current = false;
|
|
1003
|
+
setState((prevState) => {
|
|
1004
|
+
return {
|
|
1005
|
+
...prevState,
|
|
1006
|
+
isFocus: false,
|
|
1007
|
+
};
|
|
1008
|
+
});
|
|
1009
|
+
removeRootView(tag, forceRemoveAllRootViewOnItemSelected);
|
|
1010
|
+
setSearchQuery('');
|
|
1011
|
+
if (textInputRef.current) {
|
|
1012
|
+
textInputRef.current.setNativeProps({text: ''});
|
|
1013
|
+
ref_searchQuery.current = '';
|
|
1014
|
+
// Remove textInputRef.current.blur() - avoid forcing blur causing keyboard to close
|
|
1015
|
+
}
|
|
1016
|
+
// Remove Keyboard.dismiss() - let keyboard close naturally to avoid triggering keyboardDidHide event
|
|
1017
|
+
}, [tag, isKeyboardFullyShown, forceRemoveAllRootViewOnItemSelected]);
|
|
1018
|
+
|
|
1019
|
+
// Wrap TextInput independently in useMemo to recreate only when key props change
|
|
1020
|
+
// This avoids repeated ref callback triggers due to other props changes during parent component redraws
|
|
1021
|
+
const memoizedTextInput = useMemo(() => {
|
|
1022
|
+
console.log(`AutoPositionedPopup useMemo creating TextInput - tag: ${tag}, isFocus: ${state.isFocus}`);
|
|
1023
|
+
if (!useTextInput || !state.isFocus) {
|
|
1024
|
+
return null;
|
|
1025
|
+
}
|
|
1026
|
+
return (
|
|
1027
|
+
<RNTextInput
|
|
1028
|
+
ref={(ref) => {
|
|
1029
|
+
// Monitor TextInput mounting and unmounting
|
|
1030
|
+
if (ref && !textInputRef.current) {
|
|
1031
|
+
console.log(`AutoPositionedPopup TextInput created/mounted - tag: ${tag}, ref:`, ref);
|
|
1032
|
+
} else if (!ref && textInputRef.current) {
|
|
1033
|
+
console.log(`AutoPositionedPopup TextInput unmounted - tag: ${tag}`);
|
|
1034
|
+
} else if (ref && textInputRef.current && ref !== textInputRef.current) {
|
|
1035
|
+
console.log(`AutoPositionedPopup TextInput replaced - tag: ${tag}, oldRef:`, textInputRef.current, 'newRef:', ref);
|
|
1036
|
+
}
|
|
1037
|
+
textInputRef.current = ref;
|
|
1038
|
+
}}
|
|
1039
|
+
key={`textinput-${tag}`}
|
|
1040
|
+
style={[
|
|
1041
|
+
styles.inputStyle,
|
|
1042
|
+
stableInputStyle,
|
|
1043
|
+
]}
|
|
1044
|
+
textAlign={stableTextInputProps['textAlign'] || 'left'}
|
|
1045
|
+
multiline={stableTextInputProps['multiline'] || false}
|
|
1046
|
+
numberOfLines={stableTextInputProps['numberOfLines'] || 1}
|
|
1047
|
+
onChangeText={(searchQuery) => {
|
|
1048
|
+
ref_searchQuery.current = searchQuery;
|
|
1049
|
+
console.log('AutoPositionedPopup onChangeText rootViews=', rootViews);
|
|
1050
|
+
if (!localSearch) {
|
|
1051
|
+
if (debounceTimerRef.current) {
|
|
1052
|
+
clearTimeout(debounceTimerRef.current);
|
|
1053
|
+
}
|
|
1054
|
+
debounceTimerRef.current = setTimeout(() => {
|
|
1055
|
+
emitQueryChange(ref_searchQuery.current);
|
|
1056
|
+
}, 500);
|
|
1057
|
+
} else {
|
|
1058
|
+
emitQueryChange(ref_searchQuery.current);
|
|
1059
|
+
}
|
|
1060
|
+
}}
|
|
1061
|
+
placeholderTextColor={theme.colors.placeholderText}
|
|
1062
|
+
placeholder={placeholder}
|
|
1063
|
+
onKeyPress={(e) => {
|
|
1064
|
+
if (e.nativeEvent.key === 'Enter') {
|
|
1065
|
+
Keyboard.dismiss();
|
|
1066
|
+
}
|
|
1067
|
+
}}
|
|
1068
|
+
keyboardType={stableTextInputProps['keyboardType'] || 'default'}
|
|
1069
|
+
clearButtonMode="while-editing"
|
|
1070
|
+
returnKeyType={stableTextInputProps['returnKeyType'] || 'done'}
|
|
1071
|
+
maxLength={stableTextInputProps['maxLength'] || 100}
|
|
1072
|
+
accessibilityLabel="selectInput"
|
|
1073
|
+
accessible={true}
|
|
1074
|
+
autoFocus={stableTextInputProps['autoFocus'] || false}
|
|
1075
|
+
autoCorrect={false}
|
|
1076
|
+
underlineColorAndroid="transparent"
|
|
1077
|
+
editable={stableTextInputProps['editable'] || true}
|
|
1078
|
+
secureTextEntry={stableTextInputProps['secureTextEntry'] || false}
|
|
1079
|
+
defaultValue=""
|
|
1080
|
+
caretHidden={false}
|
|
1081
|
+
enablesReturnKeyAutomatically
|
|
1082
|
+
onFocus={handleTextInputFocus}
|
|
1083
|
+
onBlur={handleTextInputBlur}
|
|
1084
|
+
selectTextOnFocus={stableTextInputProps['selectTextOnFocus'] || false}
|
|
1085
|
+
onSubmitEditing={(e: NativeSyntheticEvent<TextInputSubmitEditingEventData>) => {
|
|
1086
|
+
console.log(
|
|
1087
|
+
'AutoPositionedPopup.tsx onSubmitEditing e.nativeEvent.text=',
|
|
1088
|
+
e.nativeEvent.text
|
|
1089
|
+
);
|
|
1090
|
+
onSubmitEditing && onSubmitEditing(e);
|
|
1091
|
+
}}
|
|
1092
|
+
/>
|
|
1093
|
+
);
|
|
1094
|
+
}, [
|
|
1095
|
+
tag, // tag 是稳定的
|
|
1096
|
+
useTextInput, // useTextInput 是稳定的
|
|
1097
|
+
state.isFocus, // isFocus 控制显示/隐藏
|
|
1098
|
+
handleTextInputFocus, // useCallback wrapped, reference stable
|
|
1099
|
+
handleTextInputBlur, // useCallback wrapped, reference stable
|
|
1100
|
+
stableInputStyle, // Use stable inputStyle reference (after deep comparison)
|
|
1101
|
+
stableTextInputProps, // Use stable TextInputProps reference (after deep comparison)
|
|
1102
|
+
placeholder, // placeholder usually stable
|
|
1103
|
+
onSubmitEditing, // onSubmitEditing usually stable
|
|
1104
|
+
// No longer use original inputStyle and TextInputProps, use stable references instead
|
|
1105
|
+
// Stable references only update when deep comparison detects actual content changes, avoiding frequent TextInput recreation during parent component redraws
|
|
1106
|
+
]);
|
|
1107
|
+
|
|
855
1108
|
// Render the component following project implementation
|
|
856
1109
|
return useMemo(() => {
|
|
857
1110
|
console.log('AutoPositionedPopup render tag=', tag); // Now safe - circular dependency fixed
|
|
@@ -863,22 +1116,15 @@ const AutoPositionedPopup = memo(
|
|
|
863
1116
|
style={[styles.AutoPositionedPopupBtn, AutoPositionedPopupBtnStyle]}
|
|
864
1117
|
disabled={AutoPositionedPopupBtnDisabled}
|
|
865
1118
|
onPress={() => {
|
|
866
|
-
console.log('AutoPositionedPopup onPress
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
'
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
hasShownRootView.current
|
|
876
|
-
);
|
|
877
|
-
console.log(
|
|
878
|
-
'AutoPositionedPopup onPress hasTriggeredFocus.current=',
|
|
879
|
-
hasTriggeredFocus.current
|
|
880
|
-
);
|
|
881
|
-
console.log('AutoPositionedPopup onPress state.selectedItem=', state.selectedItem);
|
|
1119
|
+
console.log('AutoPositionedPopup onPress=', {
|
|
1120
|
+
tag,
|
|
1121
|
+
'state.isFocus': state.isFocus,
|
|
1122
|
+
useTextInput,
|
|
1123
|
+
'hasAddedRootView.current': hasAddedRootView.current,
|
|
1124
|
+
'hasShownRootView.current': hasShownRootView.current,
|
|
1125
|
+
'hasTriggeredFocus.current': hasTriggeredFocus.current,
|
|
1126
|
+
'state.selectedItem': state.selectedItem
|
|
1127
|
+
});
|
|
882
1128
|
setState((prevState) => {
|
|
883
1129
|
return {
|
|
884
1130
|
...prevState,
|
|
@@ -886,6 +1132,7 @@ const AutoPositionedPopup = memo(
|
|
|
886
1132
|
};
|
|
887
1133
|
});
|
|
888
1134
|
if (!hasAddedRootView.current && useTextInput) {
|
|
1135
|
+
// TextInput version: hide first, show after keyboard is fully displayed
|
|
889
1136
|
hasAddedRootView.current = true;
|
|
890
1137
|
hasShownRootView.current = false;
|
|
891
1138
|
addRootView({
|
|
@@ -906,11 +1153,14 @@ const AutoPositionedPopup = memo(
|
|
|
906
1153
|
renderItem={renderItem}
|
|
907
1154
|
selectedItem={state.selectedItem}
|
|
908
1155
|
localSearch={localSearch}
|
|
1156
|
+
showListEmptyComponent={showListEmptyComponent}
|
|
1157
|
+
emptyText={emptyText}
|
|
909
1158
|
/>
|
|
910
1159
|
),
|
|
911
1160
|
useModal: false,
|
|
912
1161
|
});
|
|
913
1162
|
}
|
|
1163
|
+
console.log('AutoPositionedPopup onPress done')
|
|
914
1164
|
}}
|
|
915
1165
|
>
|
|
916
1166
|
{!btwChildren ? (
|
|
@@ -930,141 +1180,38 @@ const AutoPositionedPopup = memo(
|
|
|
930
1180
|
)}
|
|
931
1181
|
</TouchableOpacity>
|
|
932
1182
|
) : (
|
|
933
|
-
|
|
934
|
-
state.isFocus && (
|
|
935
|
-
<RNTextInput
|
|
936
|
-
ref={textInputRef}
|
|
937
|
-
key="fixed-textinput-key"
|
|
938
|
-
style={[
|
|
939
|
-
styles.inputStyle,
|
|
940
|
-
inputStyle,
|
|
941
|
-
]}
|
|
942
|
-
textAlign={TextInputProps['textAlign'] || 'left'}
|
|
943
|
-
multiline={TextInputProps['multiline'] || false}
|
|
944
|
-
numberOfLines={TextInputProps['numberOfLines'] || 1}
|
|
945
|
-
onChangeText={(searchQuery) => {
|
|
946
|
-
ref_searchQuery.current = searchQuery;
|
|
947
|
-
console.log('AutoPositionedPopup onChangeText rootViews=', rootViews);
|
|
948
|
-
if (!localSearch) {
|
|
949
|
-
if (debounceTimerRef.current) {
|
|
950
|
-
clearTimeout(debounceTimerRef.current);
|
|
951
|
-
}
|
|
952
|
-
debounceTimerRef.current = setTimeout(() => {
|
|
953
|
-
emitQueryChange(ref_searchQuery.current);
|
|
954
|
-
}, 500);
|
|
955
|
-
} else {
|
|
956
|
-
emitQueryChange(ref_searchQuery.current);
|
|
957
|
-
}
|
|
958
|
-
}}
|
|
959
|
-
placeholderTextColor={theme.colors.placeholderText}
|
|
960
|
-
placeholder={placeholder}
|
|
961
|
-
onKeyPress={(e) => {
|
|
962
|
-
if (e.nativeEvent.key === 'Enter') {
|
|
963
|
-
Keyboard.dismiss();
|
|
964
|
-
}
|
|
965
|
-
}}
|
|
966
|
-
keyboardType={TextInputProps['keyboardType'] || 'default'}
|
|
967
|
-
clearButtonMode="while-editing"
|
|
968
|
-
returnKeyType={TextInputProps['returnKeyType'] || 'done'}
|
|
969
|
-
maxLength={TextInputProps['maxLength'] || 100}
|
|
970
|
-
accessibilityLabel="selectInput"
|
|
971
|
-
accessible={true}
|
|
972
|
-
autoFocus={TextInputProps['autoFocus'] || false}
|
|
973
|
-
autoCorrect={false}
|
|
974
|
-
underlineColorAndroid="transparent"
|
|
975
|
-
editable={TextInputProps['editable'] || true}
|
|
976
|
-
secureTextEntry={TextInputProps['secureTextEntry'] || false}
|
|
977
|
-
defaultValue=""
|
|
978
|
-
caretHidden={false}
|
|
979
|
-
enablesReturnKeyAutomatically
|
|
980
|
-
onFocus={() => {
|
|
981
|
-
console.log(
|
|
982
|
-
'AutoPositionedPopup onFocus tag=',
|
|
983
|
-
tag,
|
|
984
|
-
' selectedItem=',
|
|
985
|
-
state.selectedItem,
|
|
986
|
-
' hasTriggeredFocus.current=',
|
|
987
|
-
hasTriggeredFocus.current,
|
|
988
|
-
' textInputRef.current=',
|
|
989
|
-
textInputRef.current,
|
|
990
|
-
' ref_searchQuery.current=',
|
|
991
|
-
ref_searchQuery.current
|
|
992
|
-
);
|
|
993
|
-
if (!hasTriggeredFocus.current) {
|
|
994
|
-
hasTriggeredFocus.current = true;
|
|
995
|
-
ref_isFocus.current = true;
|
|
996
|
-
if (state.selectedItem) {
|
|
997
|
-
ref_searchQuery.current = state.selectedItem.title;
|
|
998
|
-
}
|
|
999
|
-
if (textInputRef.current && ref_searchQuery.current) {
|
|
1000
|
-
textInputRef.current.setNativeProps({
|
|
1001
|
-
text: ref_searchQuery.current,
|
|
1002
|
-
});
|
|
1003
|
-
}
|
|
1004
|
-
}
|
|
1005
|
-
}}
|
|
1006
|
-
onBlur={() => {
|
|
1007
|
-
console.log(
|
|
1008
|
-
'AutoPositionedPopup onBlur tag=',
|
|
1009
|
-
tag,
|
|
1010
|
-
'textInputRef.current=',
|
|
1011
|
-
textInputRef.current
|
|
1012
|
-
);
|
|
1013
|
-
hasTriggeredFocus.current = false;
|
|
1014
|
-
hasAddedRootView.current = false; // 重置 RootView 狀態
|
|
1015
|
-
hasShownRootView.current = false;
|
|
1016
|
-
ref_isFocus.current = false;
|
|
1017
|
-
setState((prevState) => {
|
|
1018
|
-
return {
|
|
1019
|
-
...prevState,
|
|
1020
|
-
isFocus: false,
|
|
1021
|
-
};
|
|
1022
|
-
});
|
|
1023
|
-
removeRootView(tag, forceRemoveAllRootViewOnItemSelected);
|
|
1024
|
-
setSearchQuery('');
|
|
1025
|
-
if (textInputRef.current) {
|
|
1026
|
-
textInputRef.current.setNativeProps({text: ''});
|
|
1027
|
-
ref_searchQuery.current = '';
|
|
1028
|
-
textInputRef.current.blur();
|
|
1029
|
-
}
|
|
1030
|
-
Keyboard.dismiss();
|
|
1031
|
-
}}
|
|
1032
|
-
selectTextOnFocus={TextInputProps['selectTextOnFocus'] || false}
|
|
1033
|
-
onSubmitEditing={(e: NativeSyntheticEvent<TextInputSubmitEditingEventData>) => {
|
|
1034
|
-
console.log(
|
|
1035
|
-
'AutoPositionedPopup.tsx onSubmitEditing e.nativeEvent.text=',
|
|
1036
|
-
e.nativeEvent.text
|
|
1037
|
-
);
|
|
1038
|
-
onSubmitEditing && onSubmitEditing(e);
|
|
1039
|
-
}}
|
|
1040
|
-
/>
|
|
1041
|
-
)
|
|
1183
|
+
memoizedTextInput
|
|
1042
1184
|
)}
|
|
1043
1185
|
</View>
|
|
1044
1186
|
</CustomRow>
|
|
1045
1187
|
);
|
|
1046
|
-
}, [
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1188
|
+
}, [
|
|
1189
|
+
tag,
|
|
1190
|
+
// ✅ CRITICAL FIX: Remove all props that may change frequently or are inline functions
|
|
1191
|
+
// Changes to these props should not cause the entire component tree to recreate, especially TextInput
|
|
1192
|
+
// fetchData, // ❌ Removed: inline function
|
|
1193
|
+
// renderItem, // ❌ Removed: possibly inline function
|
|
1194
|
+
// onItemSelected, // ❌ Removed: possibly inline function
|
|
1195
|
+
// onSubmitEditing, // ❌ Removed: possibly inline function
|
|
1051
1196
|
localSearch,
|
|
1052
|
-
placeholder,
|
|
1053
|
-
textAlign,
|
|
1197
|
+
// placeholder, // ❌ Removed: may change
|
|
1198
|
+
// textAlign, // ❌ Removed: may change
|
|
1054
1199
|
pageSize,
|
|
1055
1200
|
selectedItem,
|
|
1056
|
-
CustomRow,
|
|
1201
|
+
// CustomRow, // ❌ Removed: inline function, new reference each time
|
|
1057
1202
|
useTextInput,
|
|
1058
|
-
btwChildren,
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
CustomPopViewStyle,
|
|
1203
|
+
// btwChildren, // ❌ Removed: inline function
|
|
1204
|
+
// keyExtractor, // ❌ Removed: possibly inline function
|
|
1205
|
+
// AutoPositionedPopupBtnStyle, // ❌ Removed: possibly inline object
|
|
1206
|
+
// CustomPopView, // ❌ Removed: may change
|
|
1207
|
+
// CustomPopViewStyle, // ❌ Removed: may change
|
|
1064
1208
|
forceRemoveAllRootViewOnItemSelected,
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1209
|
+
state.isFocus,
|
|
1210
|
+
showListEmptyComponent,
|
|
1211
|
+
emptyText,
|
|
1212
|
+
// ✅ Removed most dependencies that may cause re-rendering, keeping only core dependencies that truly affect component structure
|
|
1213
|
+
// This prevents TextInput recreation due to inline functions/objects during parent component redraws
|
|
1214
|
+
]);
|
|
1068
1215
|
}
|
|
1069
1216
|
)
|
|
1070
1217
|
);
|