react-native-auto-positioned-popup 1.0.4 → 1.0.5

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,OAAc,EAGZ,yBAAyB,EAEzB,mBAAmB,EAOpB,MAAM,OAAO,CAAC;AAaf,OAAO,EAAC,wBAAwB,EAAqB,MAAM,4BAA4B,CAAC;AAqLxF,QAAA,MAAM,mBAAmB,EAAE,mBAAmB,CAC5C,yBAAyB,CAAC,wBAAwB,CAAC,CA+TpD,CAAC;AAEF,eAAe,mBAAmB,CAAC"}
1
+ {"version":3,"file":"AutoPositionedPopup.d.ts","sourceRoot":"","sources":["../src/AutoPositionedPopup.tsx"],"names":[],"mappings":"AAAA,OAAc,EAGZ,yBAAyB,EAEzB,mBAAmB,EAOpB,MAAM,OAAO,CAAC;AAYf,OAAO,EAAC,wBAAwB,EAAqB,MAAM,4BAA4B,CAAC;AAgMxF,QAAA,MAAM,mBAAmB,EAAE,mBAAmB,CAC5C,yBAAyB,CAAC,wBAAwB,CAAC,CAqoBpD,CAAC;AAEF,eAAe,mBAAmB,CAAC"}
@@ -88,16 +88,73 @@ const PopupList = memo(({ data, selectedItem, onItemPress, renderItem, keyExtrac
88
88
  <AdvancedFlatList data={internalData} keyExtractor={keyExtractor} renderItem={renderItem || defaultRenderItem} keyboardShouldPersistTaps="always" showsVerticalScrollIndicator={true} nestedScrollEnabled={true}/>
89
89
  </View>);
90
90
  });
91
+ // List layout constants
92
+ const listLayout = {
93
+ height: 200,
94
+ };
91
95
  // Main AutoPositionedPopup component
92
96
  const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
93
- const { tag, style, AutoPositionedPopupBtnStyle, placeholder = 'Please Select', textAlign = 'right', onSubmitEditing, TextInputProps = {}, inputStyle, labelStyle, popUpViewStyle = { left: '5%', width: '90%' }, fetchData, renderItem, onItemSelected, localSearch = false, pageSize = 20, selectedItem, useTextInput = false, btwChildren, CustomRow = ({ children }) => <View>{children}</View>, keyExtractor = (item) => item === null || item === void 0 ? void 0 : item.id, AutoPositionedPopupBtnDisabled = false, forceRemoveAllRootViewOnItemSelected = false, centerDisplay = false, selectedItemBackgroundColor = 'rgba(116, 116, 128, 0.08)', } = props;
97
+ console.log('AutoPositionedPopup props=', props);
98
+ const { tag, style, AutoPositionedPopupBtnStyle, placeholder = 'Please Select', onSubmitEditing, TextInputProps = {}, inputStyle, labelStyle, popUpViewStyle = { left: '5%', width: '90%' }, fetchData, renderItem, onItemSelected, localSearch = false, pageSize = 20, selectedItem, useTextInput = false, btwChildren, CustomRow = ({ children }) => <View>{children}</View>, keyExtractor = (item) => item === null || item === void 0 ? void 0 : item.id, AutoPositionedPopupBtnDisabled = false, forceRemoveAllRootViewOnItemSelected = false, centerDisplay = false, selectedItemBackgroundColor = 'rgba(116, 116, 128, 0.08)', } = props;
99
+ // State management similar to project implementation
100
+ const [state, setState] = useState({
101
+ isFocus: false,
102
+ selectedItem: selectedItem,
103
+ });
94
104
  // Use RootView context
95
- const { addRootView, removeRootView, rootViews, searchQuery: contextSearchQuery, setSearchQuery: setContextSearchQuery } = useRootView();
105
+ const { addRootView, removeRootView, rootViews, setSearchQuery: setContextSearchQuery, setRootViewNativeStyle } = useRootView();
96
106
  const rootViewsRef = useRef(rootViews);
107
+ // Track TextInput focus and RootView states like project implementation
108
+ const hasTriggeredFocus = useRef(false);
109
+ const hasAddedRootView = useRef(false);
110
+ const hasShownRootView = useRef(false);
111
+ // Additional refs for keyboard and position tracking
112
+ const ref_isFocus = useRef();
113
+ const ref_isKeyboardFullyShown = useRef();
114
+ const ref_listPos = useRef();
115
+ const keyboardVisibleRef = useRef(false);
116
+ const refAutoPositionedPopup = useRef(null);
117
+ // Simple keyboard status tracking (alternative to useKeyboardStatus hook)
118
+ const [isKeyboardFullyShown, setIsKeyboardFullyShown] = useState(false);
119
+ // Keyboard listeners
120
+ useEffect(() => {
121
+ const keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', () => {
122
+ setIsKeyboardFullyShown(true);
123
+ });
124
+ const keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', () => {
125
+ setIsKeyboardFullyShown(false);
126
+ });
127
+ return () => {
128
+ keyboardDidShowListener === null || keyboardDidShowListener === void 0 ? void 0 : keyboardDidShowListener.remove();
129
+ keyboardDidHideListener === null || keyboardDidHideListener === void 0 ? void 0 : keyboardDidHideListener.remove();
130
+ };
131
+ }, []);
97
132
  useEffect(() => {
133
+ console.log('AutoPositionedPopup rootViews=', rootViews);
98
134
  rootViewsRef.current = rootViews;
135
+ if (rootViews.length === 0) {
136
+ hasAddedRootView.current = false;
137
+ hasShownRootView.current = false;
138
+ hasTriggeredFocus.current = false;
139
+ setState((prevState) => {
140
+ return Object.assign(Object.assign({}, prevState), { isFocus: false });
141
+ });
142
+ }
99
143
  }, [rootViews]);
100
- // State management
144
+ // Sync selectedItem changes like project implementation
145
+ useEffect(() => {
146
+ var _a, _b;
147
+ console.log('AutoPositionedPopup useEffect tag=', tag);
148
+ console.log('AutoPositionedPopup useEffect selectedItem=', selectedItem);
149
+ console.log('AutoPositionedPopup useEffect state.selectedItem=', state.selectedItem);
150
+ 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)) {
151
+ console.log('AutoPositionedPopup useEffect selectedItem!=state.selectedItem');
152
+ setState((prevState) => {
153
+ return Object.assign(Object.assign({}, prevState), { selectedItem: selectedItem });
154
+ });
155
+ }
156
+ }, [selectedItem, state.selectedItem, tag]);
157
+ // Legacy state for compatibility
101
158
  const [isVisible, setIsVisible] = useState(false);
102
159
  const [data, setData] = useState([]);
103
160
  const [loading, setLoading] = useState(false);
@@ -255,22 +312,38 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
255
312
  });
256
313
  }, 100);
257
314
  }, [calculatePosition, loadData, popupPosition, useTextInput, placeholder, theme, inputStyle, TextInputProps, data, selectedItem, renderItem, keyExtractor, centerDisplay, addRootView, hidePopup, handleSearchChange, handleItemPress, LIST_HEIGHT, selectedItemBackgroundColor]);
258
- // Handle button press
315
+ // Handle button press - following project implementation logic
259
316
  const handleButtonPress = useCallback(() => {
260
317
  if (AutoPositionedPopupBtnDisabled)
261
318
  return;
262
- if (useTextInput) {
263
- showPopup();
264
- // Focus text input after a short delay
265
- setTimeout(() => {
266
- var _a;
267
- (_a = textInputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
268
- }, 100);
269
- }
270
- else {
271
- showPopup();
319
+ console.log('AutoPositionedPopup onPress tag=', tag);
320
+ console.log('AutoPositionedPopup onPress state.isFocus=', state.isFocus);
321
+ console.log('AutoPositionedPopup onPress useTextInput=', useTextInput);
322
+ console.log('AutoPositionedPopup onPress hasAddedRootView.current=', hasAddedRootView.current);
323
+ console.log('AutoPositionedPopup onPress hasShownRootView.current=', hasShownRootView.current);
324
+ console.log('AutoPositionedPopup onPress hasTriggeredFocus.current=', hasTriggeredFocus.current);
325
+ console.log('AutoPositionedPopup onPress state.selectedItem=', state.selectedItem);
326
+ setState((prevState) => {
327
+ return Object.assign(Object.assign({}, prevState), { isFocus: true });
328
+ });
329
+ if (!hasAddedRootView.current && useTextInput) {
330
+ // TextInput version: hide first, show after keyboard fully appears
331
+ hasAddedRootView.current = true;
332
+ hasShownRootView.current = false;
333
+ addRootView({
334
+ id: tag,
335
+ style: {
336
+ top: 0,
337
+ left: 0,
338
+ width: popUpViewStyle === null || popUpViewStyle === void 0 ? void 0 : popUpViewStyle.width,
339
+ height: listLayout.height,
340
+ opacity: 0,
341
+ },
342
+ component: (<PopupList data={data} selectedItem={state.selectedItem} onItemPress={handleItemPress} renderItem={renderItem} keyExtractor={keyExtractor} theme={theme} rootViewsRef={rootViewsRef} fetchData={fetchData} localSearch={localSearch} pageSize={pageSize} onDataUpdate={handleDataUpdate} selectedItemBackgroundColor={selectedItemBackgroundColor}/>),
343
+ useModal: false,
344
+ });
272
345
  }
273
- }, [AutoPositionedPopupBtnDisabled, useTextInput, showPopup]);
346
+ }, [AutoPositionedPopupBtnDisabled, useTextInput, state.isFocus, state.selectedItem, tag, hasAddedRootView, hasShownRootView, hasTriggeredFocus, addRootView, popUpViewStyle, data, handleItemPress, renderItem, keyExtractor, theme, rootViewsRef, fetchData, localSearch, pageSize, handleDataUpdate, selectedItemBackgroundColor]);
274
347
  // Imperative handle for parent component access
275
348
  useImperativeHandle(parentRef, () => ({
276
349
  clearSelectedItem: () => {
@@ -280,7 +353,23 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
280
353
  showPopup,
281
354
  hidePopup,
282
355
  }), [tag, showPopup, hidePopup]);
283
- // Cleanup
356
+ // Component lifecycle management like project implementation
357
+ useEffect(() => {
358
+ console.log(`AutoPositionedPopup componentDidMount tag=`, tag);
359
+ //componentWillUnmount
360
+ return () => {
361
+ console.log(`AutoPositionedPopup componentWillUnmount tag=`, tag);
362
+ removeRootView(tag, forceRemoveAllRootViewOnItemSelected, rootViewsRef.current);
363
+ setContextSearchQuery('');
364
+ if (textInputRef.current) {
365
+ textInputRef.current.blur();
366
+ hasTriggeredFocus.current = false;
367
+ hasAddedRootView.current = false;
368
+ hasShownRootView.current = false;
369
+ }
370
+ };
371
+ }, [tag, removeRootView, forceRemoveAllRootViewOnItemSelected, setContextSearchQuery]);
372
+ // Cleanup debounce timer
284
373
  useEffect(() => {
285
374
  return () => {
286
375
  if (debounceTimerRef.current) {
@@ -288,19 +377,164 @@ const AutoPositionedPopup = memo(forwardRef((props, parentRef) => {
288
377
  }
289
378
  };
290
379
  }, []);
291
- // Render the component
292
- return (<CustomRow>
293
- <View style={[styles.contain, style]} ref={containerRef}>
294
- <TouchableOpacity style={[styles.AutoPositionedPopupBtn, AutoPositionedPopupBtnStyle]} disabled={AutoPositionedPopupBtnDisabled} onPress={handleButtonPress}>
295
- {btwChildren ? (btwChildren()) : (<Text style={[
296
- styles.searchQueryTxt,
297
- selectedItem && { color: theme.colors.text },
298
- labelStyle,
299
- ]} numberOfLines={1} ellipsizeMode="tail">
300
- {(selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.title) || placeholder}
301
- </Text>)}
302
- </TouchableOpacity>
303
- </View>
304
- </CustomRow>);
380
+ useEffect(() => {
381
+ var _a, _b;
382
+ console.log('AutoPositionedPopup useEffect tag=', tag);
383
+ console.log('AutoPositionedPopup useEffect state.isFocus=', state.isFocus);
384
+ console.log('AutoPositionedPopup useEffect isKeyboardFullyShown=', isKeyboardFullyShown);
385
+ console.log('AutoPositionedPopup useEffect ref_isFocus.current=', ref_isFocus.current);
386
+ console.log('AutoPositionedPopup useEffect ref_isKeyboardFullyShown.current=', ref_isKeyboardFullyShown.current);
387
+ console.log('AutoPositionedPopup useEffect useTextInput=', useTextInput);
388
+ console.log('AutoPositionedPopup useEffect TextInputProps=', TextInputProps);
389
+ console.log('AutoPositionedPopup useEffect hasAddedRootView.current=', hasAddedRootView.current);
390
+ console.log('AutoPositionedPopup useEffect hasShownRootView.current=', hasShownRootView.current);
391
+ if (useTextInput) {
392
+ if (isKeyboardFullyShown && hasAddedRootView.current && !hasShownRootView.current && state.isFocus) {
393
+ (_a = refAutoPositionedPopup.current) === null || _a === void 0 ? void 0 : _a.measureInWindow((x, y, width, height) => {
394
+ console.log('AutoPositionedPopup measureInWindow x=', x, ' y=', y, ' width=', width, ' height=', height);
395
+ // Get the full screen height (including the status bar and navigation bar)
396
+ const screenHeight = Dimensions.get('screen').height;
397
+ console.log('AutoPositionedPopup screenHeight=', screenHeight);
398
+ if (y + height < screenHeight / 2 - listLayout.height / 2) {
399
+ console.log('AutoPositionedPopup y + height < screenHeight / 2');
400
+ ref_listPos.current = { x: x, y: y + height, width: width };
401
+ }
402
+ else {
403
+ console.log('AutoPositionedPopup y + height >= screenHeight / 2');
404
+ ref_listPos.current = { x: x, y: y - listLayout.height, width: width };
405
+ }
406
+ console.log('AutoPositionedPopup ref_listPos.current=', ref_listPos.current);
407
+ setRootViewNativeStyle(tag, {
408
+ top: ref_listPos.current.y,
409
+ left: popUpViewStyle === null || popUpViewStyle === void 0 ? void 0 : popUpViewStyle.left,
410
+ width: popUpViewStyle === null || popUpViewStyle === void 0 ? void 0 : popUpViewStyle.width,
411
+ height: listLayout.height,
412
+ opacity: 1,
413
+ });
414
+ hasShownRootView.current = true;
415
+ });
416
+ }
417
+ else if (!isKeyboardFullyShown && ref_isFocus.current) {
418
+ console.log('AutoPositionedPopup isKeyboardFullyShown useEffect removeRootView tag=', tag, ' forceRemoveAllRootViewOnItemSelected=', forceRemoveAllRootViewOnItemSelected);
419
+ removeRootView(tag, forceRemoveAllRootViewOnItemSelected);
420
+ setState((prevState) => {
421
+ return Object.assign(Object.assign({}, prevState), { isFocus: false });
422
+ });
423
+ setContextSearchQuery('');
424
+ hasAddedRootView.current = false;
425
+ hasShownRootView.current = false;
426
+ }
427
+ }
428
+ else {
429
+ if (state.isFocus) {
430
+ (_b = refAutoPositionedPopup.current) === null || _b === void 0 ? void 0 : _b.measureInWindow((x, y, width, height) => {
431
+ console.log('AutoPositionedPopup measureInWindow x=', x, ' y=', y, ' width=', width, ' height=', height);
432
+ // Get the full screen height (including the status bar and navigation bar)
433
+ const screenHeight = Dimensions.get('screen').height;
434
+ console.log('AutoPositionedPopup screenHeight=', screenHeight);
435
+ if (y + height < screenHeight / 2 - listLayout.height / 2) {
436
+ console.log('AutoPositionedPopup y + height < screenHeight / 2');
437
+ ref_listPos.current = { x: x, y: y + height, width: width };
438
+ }
439
+ else {
440
+ console.log('AutoPositionedPopup y + height >= screenHeight / 2');
441
+ ref_listPos.current = { x: x, y: y - listLayout.height, width: width };
442
+ }
443
+ console.log('AutoPositionedPopup ref_listPos.current=', ref_listPos.current);
444
+ console.log('AutoPositionedPopup addRootView tag=', tag);
445
+ addRootView({
446
+ id: tag,
447
+ style: {
448
+ top: ref_listPos.current.y,
449
+ left: popUpViewStyle === null || popUpViewStyle === void 0 ? void 0 : popUpViewStyle.left,
450
+ width: popUpViewStyle === null || popUpViewStyle === void 0 ? void 0 : popUpViewStyle.width,
451
+ height: listLayout.height,
452
+ opacity: 1,
453
+ },
454
+ component: (<PopupList data={data} selectedItem={state.selectedItem} onItemPress={handleItemPress} renderItem={renderItem} keyExtractor={keyExtractor} theme={theme} rootViewsRef={rootViewsRef} fetchData={fetchData} localSearch={localSearch} pageSize={pageSize} onDataUpdate={handleDataUpdate} selectedItemBackgroundColor={selectedItemBackgroundColor}/>),
455
+ useModal: true,
456
+ onModalClose: () => {
457
+ console.log('AutoPositionedPopup onModalClose tag=', tag);
458
+ removeRootView(tag, forceRemoveAllRootViewOnItemSelected, rootViewsRef.current);
459
+ setState((prevState) => {
460
+ return Object.assign(Object.assign({}, prevState), { isFocus: false });
461
+ });
462
+ hasAddedRootView.current = false;
463
+ hasShownRootView.current = false;
464
+ hasTriggeredFocus.current = false;
465
+ setContextSearchQuery('');
466
+ },
467
+ centerDisplay,
468
+ });
469
+ });
470
+ }
471
+ }
472
+ if (isKeyboardFullyShown) {
473
+ ref_isFocus.current = state.isFocus;
474
+ if (isKeyboardFullyShown !== keyboardVisibleRef.current) {
475
+ keyboardVisibleRef.current = isKeyboardFullyShown;
476
+ // Ensure TextInput has correct focus when keyboard is fully shown
477
+ if (isKeyboardFullyShown && textInputRef.current) {
478
+ // Force ensure TextInput displays correct value
479
+ if (searchQueryRef.current) {
480
+ textInputRef.current.setNativeProps({ text: searchQueryRef.current });
481
+ }
482
+ }
483
+ }
484
+ }
485
+ }, [
486
+ isKeyboardFullyShown,
487
+ state.isFocus,
488
+ useTextInput,
489
+ forceRemoveAllRootViewOnItemSelected,
490
+ tag,
491
+ state.selectedItem,
492
+ popUpViewStyle,
493
+ data,
494
+ handleItemPress,
495
+ renderItem,
496
+ keyExtractor,
497
+ theme,
498
+ rootViewsRef,
499
+ fetchData,
500
+ localSearch,
501
+ pageSize,
502
+ handleDataUpdate,
503
+ selectedItemBackgroundColor,
504
+ removeRootView,
505
+ setContextSearchQuery,
506
+ addRootView,
507
+ centerDisplay,
508
+ setRootViewNativeStyle,
509
+ ]);
510
+ // Render the component following project implementation
511
+ return useMemo(() => {
512
+ var _a;
513
+ return (<CustomRow>
514
+ <View style={[styles.contain, style]} ref={refAutoPositionedPopup}>
515
+ {!state.isFocus || !useTextInput ? (<TouchableOpacity style={[styles.AutoPositionedPopupBtn, AutoPositionedPopupBtnStyle]} disabled={AutoPositionedPopupBtnDisabled} onPress={handleButtonPress}>
516
+ {!btwChildren ? (<Text style={[
517
+ styles.searchQueryTxt,
518
+ state.selectedItem && { color: theme.colors.text },
519
+ labelStyle,
520
+ ]} numberOfLines={1} ellipsizeMode={'tail'}>
521
+ {((_a = state.selectedItem) === null || _a === void 0 ? void 0 : _a.title) || placeholder}
522
+ </Text>) : (btwChildren())}
523
+ </TouchableOpacity>) : (useTextInput &&
524
+ state.isFocus && (<RNTextInput ref={textInputRef} key="fixed-textinput-key" style={[
525
+ styles.inputStyle,
526
+ {
527
+ textAlignVertical: 'center',
528
+ paddingVertical: 0,
529
+ paddingHorizontal: 0,
530
+ },
531
+ inputStyle,
532
+ ]} textAlign={TextInputProps['textAlign'] || 'left'} multiline={TextInputProps['multiline'] || false} numberOfLines={TextInputProps['numberOfLines'] || 1} placeholder={placeholder} placeholderTextColor={theme.colors.placeholderText} defaultValue={searchQueryRef.current} onChangeText={handleSearchChange} onSubmitEditing={(e) => {
533
+ onSubmitEditing === null || onSubmitEditing === void 0 ? void 0 : onSubmitEditing(e);
534
+ Keyboard.dismiss();
535
+ }} returnKeyType="done" {...TextInputProps}/>))}
536
+ </View>
537
+ </CustomRow>);
538
+ }, [state.isFocus, useTextInput, AutoPositionedPopupBtnStyle, AutoPositionedPopupBtnDisabled, handleButtonPress, btwChildren, state.selectedItem, theme.colors.text, labelStyle, placeholder, textInputRef, inputStyle, TextInputProps, searchQueryRef, handleSearchChange, onSubmitEditing, style, refAutoPositionedPopup]);
305
539
  }));
306
540
  export default AutoPositionedPopup;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-auto-positioned-popup",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "A highly customizable React Native auto-positioned popup component with search functionality and flexible styling options",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -18,11 +18,10 @@ import {
18
18
  TextInput as RNTextInput,
19
19
  TouchableOpacity,
20
20
  View,
21
- ViewStyle,
22
21
  } from 'react-native';
23
22
  import {AdvancedFlatList} from 'react-native-advanced-flatlist';
24
23
  import {TextInputSubmitEditingEventData} from 'react-native/Libraries/Components/TextInput/TextInput';
25
- import {LayoutRectangle, NativeSyntheticEvent} from 'react-native/Libraries/Types/CoreEventTypes';
24
+ import {NativeSyntheticEvent} from 'react-native/Libraries/Types/CoreEventTypes';
26
25
  import {AutoPositionedPopupProps, Data, SelectedItem} from './AutoPositionedPopupProps';
27
26
  import styles from './AutoPositionedPopup.style';
28
27
  import {useRootView} from './RootViewContext';
@@ -203,18 +202,29 @@ const PopupList: React.FC<PopupListProps> = memo(({
203
202
  );
204
203
  });
205
204
 
205
+ // State interface for AutoPositionedPopup
206
+ interface StateProps {
207
+ isFocus?: boolean;
208
+ selectedItem?: SelectedItem | any;
209
+ }
210
+
211
+ // List layout constants
212
+ const listLayout = {
213
+ height: 200,
214
+ };
215
+
206
216
  // Main AutoPositionedPopup component
207
217
  const AutoPositionedPopup: MemoExoticComponent<
208
218
  ForwardRefExoticComponent<AutoPositionedPopupProps>
209
219
  > = memo(
210
220
  forwardRef<unknown, AutoPositionedPopupProps>(
211
221
  (props: AutoPositionedPopupProps, parentRef: ForwardedRef<unknown>): React.JSX.Element => {
222
+ console.log('AutoPositionedPopup props=', props);
212
223
  const {
213
224
  tag,
214
225
  style,
215
226
  AutoPositionedPopupBtnStyle,
216
227
  placeholder = 'Please Select',
217
- textAlign = 'right',
218
228
  onSubmitEditing,
219
229
  TextInputProps = {},
220
230
  inputStyle,
@@ -236,15 +246,81 @@ const AutoPositionedPopup: MemoExoticComponent<
236
246
  selectedItemBackgroundColor = 'rgba(116, 116, 128, 0.08)',
237
247
  } = props;
238
248
 
249
+ // State management similar to project implementation
250
+ const [state, setState] = useState<StateProps>({
251
+ isFocus: false,
252
+ selectedItem: selectedItem,
253
+ });
254
+
239
255
  // Use RootView context
240
- const {addRootView, removeRootView, rootViews, searchQuery: contextSearchQuery, setSearchQuery: setContextSearchQuery} = useRootView();
256
+ const {addRootView, removeRootView, rootViews, setSearchQuery: setContextSearchQuery, setRootViewNativeStyle} = useRootView();
241
257
  const rootViewsRef = useRef(rootViews);
242
258
 
259
+ // Track TextInput focus and RootView states like project implementation
260
+ const hasTriggeredFocus = useRef(false);
261
+ const hasAddedRootView = useRef(false);
262
+ const hasShownRootView = useRef(false);
263
+
264
+ // Additional refs for keyboard and position tracking
265
+ const ref_isFocus = useRef<boolean>();
266
+ const ref_isKeyboardFullyShown = useRef<boolean>();
267
+ const ref_listPos = useRef<any>();
268
+ const keyboardVisibleRef = useRef(false);
269
+ const refAutoPositionedPopup = useRef<View>(null);
270
+
271
+ // Simple keyboard status tracking (alternative to useKeyboardStatus hook)
272
+ const [isKeyboardFullyShown, setIsKeyboardFullyShown] = useState(false);
273
+
274
+ // Keyboard listeners
275
+ useEffect(() => {
276
+ const keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', () => {
277
+ setIsKeyboardFullyShown(true);
278
+ });
279
+
280
+ const keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', () => {
281
+ setIsKeyboardFullyShown(false);
282
+ });
283
+
284
+ return () => {
285
+ keyboardDidShowListener?.remove();
286
+ keyboardDidHideListener?.remove();
287
+ };
288
+ }, []);
289
+
243
290
  useEffect(() => {
291
+ console.log('AutoPositionedPopup rootViews=', rootViews);
244
292
  rootViewsRef.current = rootViews;
293
+ if (rootViews.length === 0) {
294
+ hasAddedRootView.current = false;
295
+ hasShownRootView.current = false;
296
+ hasTriggeredFocus.current = false;
297
+ setState((prevState) => {
298
+ return {
299
+ ...prevState,
300
+ isFocus: false,
301
+ };
302
+ });
303
+ }
245
304
  }, [rootViews]);
246
305
 
247
- // State management
306
+ // Sync selectedItem changes like project implementation
307
+ useEffect(() => {
308
+ console.log('AutoPositionedPopup useEffect tag=', tag);
309
+ console.log('AutoPositionedPopup useEffect selectedItem=', selectedItem);
310
+ console.log('AutoPositionedPopup useEffect state.selectedItem=', state.selectedItem);
311
+ if (state.selectedItem?.id !== selectedItem?.id || state.selectedItem?.title !== selectedItem?.title) {
312
+ console.log('AutoPositionedPopup useEffect selectedItem!=state.selectedItem');
313
+ setState((prevState) => {
314
+ return {
315
+ ...prevState,
316
+ selectedItem: selectedItem,
317
+ };
318
+ });
319
+ }
320
+ }, [selectedItem, state.selectedItem, tag]);
321
+
322
+
323
+ // Legacy state for compatibility
248
324
  const [isVisible, setIsVisible] = useState(false);
249
325
  const [data, setData] = useState<SelectedItem[]>([]);
250
326
  const [loading, setLoading] = useState(false);
@@ -456,20 +532,67 @@ const AutoPositionedPopup: MemoExoticComponent<
456
532
  }, 100);
457
533
  }, [calculatePosition, loadData, popupPosition, useTextInput, placeholder, theme, inputStyle, TextInputProps, data, selectedItem, renderItem, keyExtractor, centerDisplay, addRootView, hidePopup, handleSearchChange, handleItemPress, LIST_HEIGHT, selectedItemBackgroundColor]);
458
534
 
459
- // Handle button press
535
+ // Handle button press - following project implementation logic
460
536
  const handleButtonPress = useCallback(() => {
461
537
  if (AutoPositionedPopupBtnDisabled) return;
462
538
 
463
- if (useTextInput) {
464
- showPopup();
465
- // Focus text input after a short delay
466
- setTimeout(() => {
467
- textInputRef.current?.focus();
468
- }, 100);
469
- } else {
470
- showPopup();
539
+ console.log('AutoPositionedPopup onPress tag=', tag);
540
+ console.log('AutoPositionedPopup onPress state.isFocus=', state.isFocus);
541
+ console.log('AutoPositionedPopup onPress useTextInput=', useTextInput);
542
+ console.log(
543
+ 'AutoPositionedPopup onPress hasAddedRootView.current=',
544
+ hasAddedRootView.current
545
+ );
546
+ console.log(
547
+ 'AutoPositionedPopup onPress hasShownRootView.current=',
548
+ hasShownRootView.current
549
+ );
550
+ console.log(
551
+ 'AutoPositionedPopup onPress hasTriggeredFocus.current=',
552
+ hasTriggeredFocus.current
553
+ );
554
+ console.log('AutoPositionedPopup onPress state.selectedItem=', state.selectedItem);
555
+
556
+ setState((prevState) => {
557
+ return {
558
+ ...prevState,
559
+ isFocus: true,
560
+ };
561
+ });
562
+
563
+ if (!hasAddedRootView.current && useTextInput) {
564
+ // TextInput version: hide first, show after keyboard fully appears
565
+ hasAddedRootView.current = true;
566
+ hasShownRootView.current = false;
567
+ addRootView({
568
+ id: tag,
569
+ style: {
570
+ top: 0,
571
+ left: 0,
572
+ width: popUpViewStyle?.width,
573
+ height: listLayout.height,
574
+ opacity: 0,
575
+ },
576
+ component: (
577
+ <PopupList
578
+ data={data}
579
+ selectedItem={state.selectedItem}
580
+ onItemPress={handleItemPress}
581
+ renderItem={renderItem}
582
+ keyExtractor={keyExtractor}
583
+ theme={theme}
584
+ rootViewsRef={rootViewsRef}
585
+ fetchData={fetchData}
586
+ localSearch={localSearch}
587
+ pageSize={pageSize}
588
+ onDataUpdate={handleDataUpdate}
589
+ selectedItemBackgroundColor={selectedItemBackgroundColor}
590
+ />
591
+ ),
592
+ useModal: false,
593
+ });
471
594
  }
472
- }, [AutoPositionedPopupBtnDisabled, useTextInput, showPopup]);
595
+ }, [AutoPositionedPopupBtnDisabled, useTextInput, state.isFocus, state.selectedItem, tag, hasAddedRootView, hasShownRootView, hasTriggeredFocus, addRootView, popUpViewStyle, data, handleItemPress, renderItem, keyExtractor, theme, rootViewsRef, fetchData, localSearch, pageSize, handleDataUpdate, selectedItemBackgroundColor]);
473
596
 
474
597
  // Imperative handle for parent component access
475
598
  useImperativeHandle(
@@ -485,7 +608,25 @@ const AutoPositionedPopup: MemoExoticComponent<
485
608
  [tag, showPopup, hidePopup]
486
609
  );
487
610
 
488
- // Cleanup
611
+ // Component lifecycle management like project implementation
612
+ useEffect(() => {
613
+ console.log(`AutoPositionedPopup componentDidMount tag=`, tag);
614
+
615
+ //componentWillUnmount
616
+ return () => {
617
+ console.log(`AutoPositionedPopup componentWillUnmount tag=`, tag);
618
+ removeRootView(tag, forceRemoveAllRootViewOnItemSelected, rootViewsRef.current);
619
+ setContextSearchQuery('');
620
+ if (textInputRef.current) {
621
+ textInputRef.current.blur();
622
+ hasTriggeredFocus.current = false;
623
+ hasAddedRootView.current = false;
624
+ hasShownRootView.current = false;
625
+ }
626
+ };
627
+ }, [tag, removeRootView, forceRemoveAllRootViewOnItemSelected, setContextSearchQuery]);
628
+
629
+ // Cleanup debounce timer
489
630
  useEffect(() => {
490
631
  return () => {
491
632
  if (debounceTimerRef.current) {
@@ -494,34 +635,229 @@ const AutoPositionedPopup: MemoExoticComponent<
494
635
  };
495
636
  }, []);
496
637
 
497
- // Render the component
498
- return (
499
- <CustomRow>
500
- <View style={[styles.contain, style]} ref={containerRef}>
501
- <TouchableOpacity
502
- style={[styles.AutoPositionedPopupBtn, AutoPositionedPopupBtnStyle]}
503
- disabled={AutoPositionedPopupBtnDisabled}
504
- onPress={handleButtonPress}
505
- >
506
- {btwChildren ? (
507
- btwChildren()
508
- ) : (
509
- <Text
510
- style={[
511
- styles.searchQueryTxt,
512
- selectedItem && {color: theme.colors.text},
513
- labelStyle,
514
- ]}
515
- numberOfLines={1}
516
- ellipsizeMode="tail"
638
+ useEffect(() => {
639
+ console.log('AutoPositionedPopup useEffect tag=', tag);
640
+ console.log('AutoPositionedPopup useEffect state.isFocus=', state.isFocus);
641
+ console.log('AutoPositionedPopup useEffect isKeyboardFullyShown=', isKeyboardFullyShown);
642
+ console.log('AutoPositionedPopup useEffect ref_isFocus.current=', ref_isFocus.current);
643
+ console.log(
644
+ 'AutoPositionedPopup useEffect ref_isKeyboardFullyShown.current=',
645
+ ref_isKeyboardFullyShown.current
646
+ );
647
+ console.log('AutoPositionedPopup useEffect useTextInput=', useTextInput);
648
+ console.log('AutoPositionedPopup useEffect TextInputProps=', TextInputProps);
649
+ console.log('AutoPositionedPopup useEffect hasAddedRootView.current=', hasAddedRootView.current);
650
+ console.log('AutoPositionedPopup useEffect hasShownRootView.current=', hasShownRootView.current);
651
+
652
+ if (useTextInput) {
653
+ if (isKeyboardFullyShown && hasAddedRootView.current && !hasShownRootView.current && state.isFocus) {
654
+ refAutoPositionedPopup.current?.measureInWindow((x: number, y: number, width: number, height: number) => {
655
+ console.log('AutoPositionedPopup measureInWindow x=', x, ' y=', y, ' width=', width, ' height=', height);
656
+ // Get the full screen height (including the status bar and navigation bar)
657
+ const screenHeight = Dimensions.get('screen').height;
658
+ console.log('AutoPositionedPopup screenHeight=', screenHeight);
659
+
660
+ if (y + height < screenHeight / 2 - listLayout.height / 2) {
661
+ console.log('AutoPositionedPopup y + height < screenHeight / 2');
662
+ ref_listPos.current = { x: x, y: y + height, width: width };
663
+ } else {
664
+ console.log('AutoPositionedPopup y + height >= screenHeight / 2');
665
+ ref_listPos.current = { x: x, y: y - listLayout.height, width: width };
666
+ }
667
+ console.log('AutoPositionedPopup ref_listPos.current=', ref_listPos.current);
668
+
669
+ setRootViewNativeStyle(tag, {
670
+ top: ref_listPos.current.y,
671
+ left: popUpViewStyle?.left,
672
+ width: popUpViewStyle?.width,
673
+ height: listLayout.height,
674
+ opacity: 1,
675
+ });
676
+ hasShownRootView.current = true;
677
+ });
678
+ } else if (!isKeyboardFullyShown && ref_isFocus.current) {
679
+ console.log(
680
+ 'AutoPositionedPopup isKeyboardFullyShown useEffect removeRootView tag=',
681
+ tag,
682
+ ' forceRemoveAllRootViewOnItemSelected=',
683
+ forceRemoveAllRootViewOnItemSelected
684
+ );
685
+ removeRootView(tag, forceRemoveAllRootViewOnItemSelected);
686
+ setState((prevState) => {
687
+ return {
688
+ ...prevState,
689
+ isFocus: false,
690
+ };
691
+ });
692
+ setContextSearchQuery('');
693
+ hasAddedRootView.current = false;
694
+ hasShownRootView.current = false;
695
+ }
696
+ } else {
697
+ if (state.isFocus) {
698
+ refAutoPositionedPopup.current?.measureInWindow((x: number, y: number, width: number, height: number) => {
699
+ console.log('AutoPositionedPopup measureInWindow x=', x, ' y=', y, ' width=', width, ' height=', height);
700
+ // Get the full screen height (including the status bar and navigation bar)
701
+ const screenHeight = Dimensions.get('screen').height;
702
+ console.log('AutoPositionedPopup screenHeight=', screenHeight);
703
+
704
+ if (y + height < screenHeight / 2 - listLayout.height / 2) {
705
+ console.log('AutoPositionedPopup y + height < screenHeight / 2');
706
+ ref_listPos.current = { x: x, y: y + height, width: width };
707
+ } else {
708
+ console.log('AutoPositionedPopup y + height >= screenHeight / 2');
709
+ ref_listPos.current = { x: x, y: y - listLayout.height, width: width };
710
+ }
711
+ console.log('AutoPositionedPopup ref_listPos.current=', ref_listPos.current);
712
+
713
+ console.log('AutoPositionedPopup addRootView tag=', tag);
714
+ addRootView({
715
+ id: tag,
716
+ style: {
717
+ top: ref_listPos.current.y,
718
+ left: popUpViewStyle?.left,
719
+ width: popUpViewStyle?.width,
720
+ height: listLayout.height,
721
+ opacity: 1,
722
+ },
723
+ component: (
724
+ <PopupList
725
+ data={data}
726
+ selectedItem={state.selectedItem}
727
+ onItemPress={handleItemPress}
728
+ renderItem={renderItem}
729
+ keyExtractor={keyExtractor}
730
+ theme={theme}
731
+ rootViewsRef={rootViewsRef}
732
+ fetchData={fetchData}
733
+ localSearch={localSearch}
734
+ pageSize={pageSize}
735
+ onDataUpdate={handleDataUpdate}
736
+ selectedItemBackgroundColor={selectedItemBackgroundColor}
737
+ />
738
+ ),
739
+ useModal: true,
740
+ onModalClose: () => {
741
+ console.log('AutoPositionedPopup onModalClose tag=', tag);
742
+ removeRootView(tag, forceRemoveAllRootViewOnItemSelected, rootViewsRef.current);
743
+ setState((prevState) => {
744
+ return {
745
+ ...prevState,
746
+ isFocus: false,
747
+ };
748
+ });
749
+ hasAddedRootView.current = false;
750
+ hasShownRootView.current = false;
751
+ hasTriggeredFocus.current = false;
752
+ setContextSearchQuery('');
753
+ },
754
+ centerDisplay,
755
+ });
756
+ });
757
+ }
758
+ }
759
+
760
+ if (isKeyboardFullyShown) {
761
+ ref_isFocus.current = state.isFocus;
762
+ if (isKeyboardFullyShown !== keyboardVisibleRef.current) {
763
+ keyboardVisibleRef.current = isKeyboardFullyShown;
764
+ // Ensure TextInput has correct focus when keyboard is fully shown
765
+ if (isKeyboardFullyShown && textInputRef.current) {
766
+ // Force ensure TextInput displays correct value
767
+ if (searchQueryRef.current) {
768
+ textInputRef.current.setNativeProps({ text: searchQueryRef.current });
769
+ }
770
+ }
771
+ }
772
+ }
773
+ }, [
774
+ isKeyboardFullyShown,
775
+ state.isFocus,
776
+ useTextInput,
777
+ forceRemoveAllRootViewOnItemSelected,
778
+ tag,
779
+ state.selectedItem,
780
+ popUpViewStyle,
781
+ data,
782
+ handleItemPress,
783
+ renderItem,
784
+ keyExtractor,
785
+ theme,
786
+ rootViewsRef,
787
+ fetchData,
788
+ localSearch,
789
+ pageSize,
790
+ handleDataUpdate,
791
+ selectedItemBackgroundColor,
792
+ removeRootView,
793
+ setContextSearchQuery,
794
+ addRootView,
795
+ centerDisplay,
796
+ setRootViewNativeStyle,
797
+ ]);
798
+
799
+ // Render the component following project implementation
800
+ return useMemo(() => {
801
+ return (
802
+ <CustomRow>
803
+ <View style={[styles.contain, style]} ref={refAutoPositionedPopup}>
804
+ {!state.isFocus || !useTextInput ? (
805
+ <TouchableOpacity
806
+ style={[styles.AutoPositionedPopupBtn, AutoPositionedPopupBtnStyle]}
807
+ disabled={AutoPositionedPopupBtnDisabled}
808
+ onPress={handleButtonPress}
517
809
  >
518
- {selectedItem?.title || placeholder}
519
- </Text>
810
+ {!btwChildren ? (
811
+ <Text
812
+ style={[
813
+ styles.searchQueryTxt,
814
+ state.selectedItem && { color: theme.colors.text },
815
+ labelStyle,
816
+ ]}
817
+ numberOfLines={1}
818
+ ellipsizeMode={'tail'}
819
+ >
820
+ {state.selectedItem?.title || placeholder}
821
+ </Text>
822
+ ) : (
823
+ btwChildren()
824
+ )}
825
+ </TouchableOpacity>
826
+ ) : (
827
+ useTextInput &&
828
+ state.isFocus && (
829
+ <RNTextInput
830
+ ref={textInputRef}
831
+ key="fixed-textinput-key"
832
+ style={[
833
+ styles.inputStyle,
834
+ {
835
+ textAlignVertical: 'center',
836
+ paddingVertical: 0,
837
+ paddingHorizontal: 0,
838
+ },
839
+ inputStyle,
840
+ ]}
841
+ textAlign={TextInputProps['textAlign'] || 'left'}
842
+ multiline={TextInputProps['multiline'] || false}
843
+ numberOfLines={TextInputProps['numberOfLines'] || 1}
844
+ placeholder={placeholder}
845
+ placeholderTextColor={theme.colors.placeholderText}
846
+ defaultValue={searchQueryRef.current}
847
+ onChangeText={handleSearchChange}
848
+ onSubmitEditing={(e: NativeSyntheticEvent<TextInputSubmitEditingEventData>) => {
849
+ onSubmitEditing?.(e);
850
+ Keyboard.dismiss();
851
+ }}
852
+ returnKeyType="done"
853
+ {...TextInputProps}
854
+ />
855
+ )
520
856
  )}
521
- </TouchableOpacity>
522
- </View>
523
- </CustomRow>
524
- );
857
+ </View>
858
+ </CustomRow>
859
+ );
860
+ }, [state.isFocus, useTextInput, AutoPositionedPopupBtnStyle, AutoPositionedPopupBtnDisabled, handleButtonPress, btwChildren, state.selectedItem, theme.colors.text, labelStyle, placeholder, textInputRef, inputStyle, TextInputProps, searchQueryRef, handleSearchChange, onSubmitEditing, style, refAutoPositionedPopup]);
525
861
  }
526
862
  )
527
863
  );