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

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,8 +1,13 @@
1
1
  import React, { forwardRef, memo, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState, } from 'react';
2
2
  import { Dimensions, Keyboard, Text, TextInput as RNTextInput, TouchableOpacity, View, } from 'react-native';
3
- import { AdvancedFlatList } from 'react-native-advanced-flatlist';
3
+ // @ts-ignore - Skip type checking for third-party library with type issues
4
+ import { AdvancedFlatList as AdvancedFlatListLib } from 'react-native-advanced-flatlist';
5
+ // @ts-ignore - Direct import from source when using fake data
6
+ import AdvancedFlatListSource from 'react-native-advanced-flatlist/src/AdvancedFlatList.tsx';
4
7
  import styles from './AutoPositionedPopup.style';
5
8
  import { useRootView } from './RootViewContext';
9
+ import { useKeyboardStatus } from './KeyboardManager';
10
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
6
11
  const queryChangeListeners = [];
7
12
  const emitQueryChange = (query) => {
8
13
  console.log('AutoPositionedPopup.tsx emitQueryChange query=', query, ' listeners=', queryChangeListeners.length);
@@ -26,281 +31,555 @@ const defaultTheme = {
26
31
  },
27
32
  };
28
33
  // List item component for rendering individual items
29
- const ListItem = memo(({ item, index, selectedItem, onItemPress, theme, rootViewsRef, selectedItemBackgroundColor = 'rgba(116, 116, 128, 0.08)' }) => {
30
- const isSelected = item.id === (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.id);
31
- return useMemo(() => (<TouchableOpacity key={item.id} style={[
32
- styles.commonModalRow,
33
- {
34
- backgroundColor: isSelected ? selectedItemBackgroundColor : 'transparent',
35
- borderColor: theme.colors.border,
36
- },
37
- ]} onPress={() => {
38
- console.log('AutoPositionedPopup.tsx ListItem onPress item=', item);
39
- if (rootViewsRef) {
40
- console.log('AutoPositionedPopup.tsx ListItem onPress rootViews=', rootViewsRef.current);
41
- }
42
- onItemPress(item);
43
- }}>
44
- <Text style={[styles.ListItemCode, { color: theme.colors.text }]} numberOfLines={1} ellipsizeMode="tail">
45
- {item.title}
46
- </Text>
47
- </TouchableOpacity>), [item, index, selectedItem, onItemPress, theme, rootViewsRef, isSelected, selectedItemBackgroundColor]);
34
+ const ListItem = memo(({ updateState, item, index, selectedItem, }) => {
35
+ const { addRootView, setRootViewNativeStyle, removeRootView, rootViews } = useRootView();
36
+ const rootViewsRef = useRef(rootViews);
37
+ useEffect(() => {
38
+ rootViewsRef.current = rootViews;
39
+ }, [rootViews]);
40
+ return useMemo(() => {
41
+ // console.log('AutoPositionedPopup.tsx ListItem index=', index);
42
+ // console.log('AutoPositionedPopup.tsx ListItem item=', item);
43
+ const isSelected = item.id === (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.id);
44
+ return (<TouchableOpacity key={item.id} style={[
45
+ styles.commonModalRow,
46
+ { backgroundColor: isSelected ? 'rgba(116, 116, 128, 0.08)' : 'transparent' },
47
+ ]} onPress={() => {
48
+ // console.log('AutoPositionedPopup.tsx ListItem onPress item=', item); // Commented to prevent spam
49
+ // console.log('AutoPositionedPopup.tsx ListItem onPress rootViews=', rootViewsRef.current); // Commented to prevent spam
50
+ updateState('selectedItem', item);
51
+ }}>
52
+ <Text style={styles.ListItemCode} numberOfLines={1} ellipsizeMode="tail">
53
+ {item.title}
54
+ </Text>
55
+ </TouchableOpacity>);
56
+ }, [updateState, item, index, selectedItem, rootViewsRef]);
48
57
  });
49
- const PopupList = memo(({ data, selectedItem, onItemPress, renderItem, keyExtractor = (item) => String(item.id), theme, rootViewsRef, fetchData, localSearch = false, pageSize = 20, onDataUpdate, selectedItemBackgroundColor, }) => {
50
- const [internalData, setInternalData] = useState(data);
51
- const searchQueryRef = useRef('');
52
- // Sync external data changes
58
+ const AutoPositionedPopupList = memo(({ tag, updateState, fetchData, keyExtractor = (item) => String(item.id), renderItem, selectedItem, localSearch, pageSize, }) => {
59
+ const [state, setState] = useState({
60
+ selectedItem: selectedItem,
61
+ localData: [],
62
+ });
63
+ // Define an interface that matches the methods we need from CsxFlatList
64
+ const ref_list = useRef(null);
65
+ const ref_searchQuery = useRef('');
66
+ const { searchQuery, setSearchQuery, rootViews } = useRootView();
67
+ const rootViewsRef = useRef(rootViews);
53
68
  useEffect(() => {
54
- setInternalData(data);
55
- }, [data]);
56
- // Listen to search query changes
69
+ rootViewsRef.current = rootViews;
70
+ }, [rootViews]);
71
+ /**
72
+ * componentDidMount && componentWillUnmount
73
+ */
57
74
  useEffect(() => {
58
- const unsubscribe = subscribeQueryChange(async (newQuery) => {
59
- console.log('PopupList subscribeQueryChange newQuery=', newQuery);
60
- searchQueryRef.current = newQuery;
61
- if (fetchData) {
62
- try {
63
- const result = await fetchData({
64
- pageIndex: 0,
65
- pageSize,
66
- searchQuery: newQuery,
67
- });
68
- if (result === null || result === void 0 ? void 0 : result.items) {
69
- setInternalData(result.items);
70
- onDataUpdate === null || onDataUpdate === void 0 ? void 0 : onDataUpdate(result.items);
71
- }
72
- }
73
- catch (error) {
74
- console.error('PopupList fetchData error:', error);
75
- }
76
- }
77
- else if (localSearch) {
78
- // Local filtering
79
- const filtered = data.filter(item => item.title.toLowerCase().includes(newQuery.toLowerCase()));
80
- setInternalData(filtered);
81
- onDataUpdate === null || onDataUpdate === void 0 ? void 0 : onDataUpdate(filtered);
75
+ (async () => {
76
+ })();
77
+ console.log(`AutoPositionedPopupList componentDidMount`);
78
+ //componentWillUnmount
79
+ return () => {
80
+ console.log(`AutoPositionedPopupList componentWillUnmount`);
81
+ setSearchQuery('');
82
+ };
83
+ }, []);
84
+ // useEffect(() => {
85
+ // // 監聽 TextInput 事件,收到就刷新列表,不依賴 global searchQuery
86
+ // // 將最新的 searchQuery 同步到 list 專用的 ref,供 _fetchData 使用
87
+ // ref_searchQuery.current = searchQuery;
88
+ // console.log('AutoPositionedPopupList useEffect searchQuery=', searchQuery);
89
+ // console.log('AutoPositionedPopupList useEffect state.localData=', state.localData);
90
+ // console.log('AutoPositionedPopupList useEffect ref_list.current=', ref_list.current);
91
+ // console.log('AutoPositionedPopupList useEffect localSearch=', localSearch);
92
+ // if (ref_list.current && (localSearch && state.localData.length > 0 || !localSearch)) {
93
+ // ref_list.current.scrollToTop();
94
+ // ref_list.current.refresh();
95
+ // }
96
+ // }, [searchQuery, state.localData, localSearch]);
97
+ useEffect(() => {
98
+ const unsubscribe = subscribeQueryChange((newQuery) => {
99
+ console.log('AutoPositionedPopupList useEffect subscribeQueryChange newQuery=', newQuery);
100
+ ref_searchQuery.current = newQuery;
101
+ if (ref_list.current) {
102
+ ref_list.current.scrollToTop();
103
+ ref_list.current.refresh();
82
104
  }
83
105
  });
84
106
  return unsubscribe;
85
- }, [fetchData, localSearch, pageSize, data, onDataUpdate]);
86
- const defaultRenderItem = useCallback(({ item, index }) => (<ListItem item={item} index={index} selectedItem={selectedItem} onItemPress={onItemPress} theme={theme} rootViewsRef={rootViewsRef} selectedItemBackgroundColor={selectedItemBackgroundColor}/>), [selectedItem, onItemPress, theme, rootViewsRef, selectedItemBackgroundColor]);
87
- return (<View style={[styles.autoPositionedPopupList, { backgroundColor: theme.colors.background }]}>
88
- <AdvancedFlatList data={internalData} keyExtractor={keyExtractor} renderItem={renderItem || defaultRenderItem} keyboardShouldPersistTaps="always" showsVerticalScrollIndicator={true} nestedScrollEnabled={true}/>
89
- </View>);
107
+ }, []);
108
+ const _updateState = (key, value) => {
109
+ console.log('AutoPositionedPopupList _updateState key=', key, ' value=', value);
110
+ setState((prevState) => (Object.assign(Object.assign({}, prevState), { [key]: value })));
111
+ console.log('AutoPositionedPopupList _updateState rootViews=', rootViewsRef.current);
112
+ updateState(key, value);
113
+ };
114
+ const _fetchData = async ({ pageIndex, pageSize: currentPageSize, }) => {
115
+ console.log('AutoPositionedPopupList _fetchData pageIndex=', pageIndex, ' pageSize=', currentPageSize);
116
+ console.log('AutoPositionedPopupList _fetchData state.localData=', state.localData);
117
+ console.log('AutoPositionedPopupList _fetchData ref_searchQuery.current=', ref_searchQuery.current);
118
+ console.log('AutoPositionedPopupList _fetchData localSearch=', localSearch);
119
+ if (localSearch && state.localData.length > 0) {
120
+ const result = state.localData.filter((item) => {
121
+ var _a;
122
+ return (_a = item.title) === null || _a === void 0 ? void 0 : _a.toLowerCase().includes(ref_searchQuery.current.toLowerCase());
123
+ });
124
+ console.log('AutoPositionedPopupList _fetchData localSearch result=', result);
125
+ return Promise.resolve({
126
+ items: result,
127
+ pageIndex: 0,
128
+ needLoadMore: false,
129
+ });
130
+ }
131
+ try {
132
+ const res = await fetchData({
133
+ pageIndex,
134
+ pageSize: pageSize || 10,
135
+ searchQuery: ref_searchQuery.current,
136
+ });
137
+ console.log('AutoPositionedPopupList _fetchData res=', res);
138
+ if ((res === null || res === void 0 ? void 0 : res.items) && localSearch) {
139
+ setState((prevState) => {
140
+ return Object.assign(Object.assign({}, prevState), { localData: res.items });
141
+ });
142
+ }
143
+ return Promise.resolve(res);
144
+ }
145
+ catch (e) {
146
+ console.warn('Error in fetchData:', e);
147
+ }
148
+ console.log('AutoPositionedPopupList _fetchData res=', null);
149
+ return null;
150
+ };
151
+ const _renderItem = useCallback(({ item, index }) => {
152
+ return <ListItem item={item} index={index} updateState={_updateState} selectedItem={state.selectedItem}/>;
153
+ }, [state.selectedItem]);
154
+ return useMemo(() => {
155
+ console.log('AutoPositionedPopupList (global as any)?.$fake=', global === null || global === void 0 ? void 0 : global.$fake);
156
+ // Choose AdvancedFlatList version based on global.$fake
157
+ const AdvancedFlatListComponent = (global === null || global === void 0 ? void 0 : global.$fake) ? AdvancedFlatListSource : AdvancedFlatListLib;
158
+ return (<View style={[styles.baseModalView, styles.autoPositionedPopupList]}>
159
+ {/* @ts-ignore - Type assertion to bypass third-party library type issues */}
160
+ <AdvancedFlatListComponent style={[{ borderRadius: 0 }]} {...(ref_list && { ref: ref_list })} keyExtractor={keyExtractor} keyboardShouldPersistTaps={'always'} {...({ fetchData: _fetchData })} renderItem={renderItem || _renderItem}/>
161
+ </View>);
162
+ }, [tag,
163
+ updateState,
164
+ fetchData,
165
+ keyExtractor,
166
+ renderItem,
167
+ state.selectedItem,
168
+ state.localData,
169
+ searchQuery,
170
+ localSearch,
171
+ pageSize,
172
+ rootViewsRef,
173
+ ]);
90
174
  });
175
+ // List layout constants
176
+ const listLayout = {
177
+ height: 200,
178
+ };
91
179
  // Main AutoPositionedPopup component
92
180
  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;
181
+ console.log('AutoPositionedPopup props=', props);
182
+ const { tag, style, AutoPositionedPopupBtnStyle, placeholder = 'Please Select', onSubmitEditing, TextInputProps = {}, inputStyle, labelStyle, popUpViewStyle = { left: '5%', width: '90%' }, fetchData = async ({ pageIndex, pageSize, searchQuery, }) => {
183
+ const res = {
184
+ items: [],
185
+ pageIndex,
186
+ needLoadMore: false,
187
+ };
188
+ try {
189
+ // const res1: any[] = await $api.xxx(pageSize)
190
+ // console.log('${NAME} xxx res=', res)
191
+ // res.items = res1
192
+ // res.needLoadMore = res1.length === pageSize
193
+ }
194
+ catch (e) {
195
+ console.warn('Error in fetch operation:', e);
196
+ }
197
+ return res;
198
+ }, renderItem, onItemSelected, localSearch = false, pageSize = 20, selectedItem, useTextInput = false, btwChildren, CustomRow = ({ children }) => <View>{children}</View>, keyExtractor = (item) => String((item === null || item === void 0 ? void 0 : item.id) || ''), AutoPositionedPopupBtnDisabled = false, forceRemoveAllRootViewOnItemSelected = false, centerDisplay = false, selectedItemBackgroundColor = 'rgba(116, 116, 128, 0.08)', textAlign = 'right', CustomPopView = undefined, CustomPopViewStyle } = props;
199
+ // State management similar to project implementation
200
+ const [state, setState] = useState({
201
+ isFocus: false,
202
+ selectedItem: selectedItem,
203
+ });
94
204
  // Use RootView context
95
- const { addRootView, removeRootView, rootViews, searchQuery: contextSearchQuery, setSearchQuery: setContextSearchQuery } = useRootView();
205
+ const { addRootView, setRootViewNativeStyle, removeRootView, rootViews, setSearchQuery } = useRootView();
206
+ const insets = useSafeAreaInsets();
96
207
  const rootViewsRef = useRef(rootViews);
97
- useEffect(() => {
98
- rootViewsRef.current = rootViews;
99
- }, [rootViews]);
100
- // State management
208
+ // Track TextInput focus and RootView states like project implementation
209
+ const hasTriggeredFocus = useRef(false);
210
+ const hasAddedRootView = useRef(false);
211
+ const hasShownRootView = useRef(false);
212
+ // Additional refs for keyboard and position tracking
213
+ const ref_isFocus = useRef();
214
+ const ref_isKeyboardFullyShown = useRef();
215
+ const ref_listPos = useRef();
216
+ const keyboardVisibleRef = useRef(false);
217
+ const refAutoPositionedPopup = useRef(null);
218
+ const ref_searchQuery = useRef('');
219
+ // Simple keyboard status tracking (alternative to useKeyboardStatus hook)
220
+ // Legacy state for compatibility
101
221
  const [isVisible, setIsVisible] = useState(false);
102
222
  const [data, setData] = useState([]);
103
223
  const [loading, setLoading] = useState(false);
104
224
  const [popupPosition, setPopupPosition] = useState({ top: 0, left: 0, width: 0 });
105
- const popupId = useRef(`popup-${tag}-${Date.now()}`);
106
225
  // Refs for performance optimization
107
226
  const containerRef = useRef(null);
108
227
  const textInputRef = useRef(null);
109
228
  const debounceTimerRef = useRef(null);
110
229
  const searchQueryRef = useRef(''); // Use ref instead of state to avoid re-renders
111
- // Constants
112
- const LIST_HEIGHT = 200;
230
+ // Refs to store latest values for useEffect without adding to dependency array
231
+ const dataRef = useRef(data);
232
+ const isKeyboardFullyShown = useKeyboardStatus();
113
233
  const theme = defaultTheme;
114
- // Fetch data function
115
- const loadData = useCallback(async (query = '') => {
116
- if (!fetchData)
117
- return;
118
- setLoading(true);
119
- try {
120
- const result = await fetchData({
121
- pageIndex: 0,
122
- pageSize,
123
- searchQuery: query,
124
- });
125
- if (result === null || result === void 0 ? void 0 : result.items) {
126
- setData(result.items);
234
+ /**
235
+ * componentDidMount && componentWillUnmount
236
+ */
237
+ useEffect(() => {
238
+ (async () => {
239
+ })();
240
+ console.log(`AutoPositionedPopup componentDidMount tag=`, tag);
241
+ console.log('AutoPositionedPopup componentDidMount CustomPopView=', CustomPopView);
242
+ //componentWillUnmount
243
+ return () => {
244
+ console.log(`AutoPositionedPopup componentWillUnmount tag=`, tag);
245
+ removeRootView(tag, forceRemoveAllRootViewOnItemSelected, rootViewsRef.current);
246
+ setSearchQuery('');
247
+ if (textInputRef.current) {
248
+ textInputRef.current.blur();
249
+ hasTriggeredFocus.current = false;
250
+ hasAddedRootView.current = false;
251
+ hasShownRootView.current = false;
252
+ ref_isFocus.current = false;
127
253
  }
128
- }
129
- catch (error) {
130
- console.error('Error loading data:', error);
131
- }
132
- finally {
133
- setLoading(false);
134
- }
135
- }, [fetchData, pageSize]);
136
- // Handle search query change with debounce and event emission
137
- const handleSearchChange = useCallback((query) => {
138
- // Store in ref to avoid re-renders
139
- searchQueryRef.current = query;
140
- // Update TextInput value directly if needed
141
- if (textInputRef.current) {
142
- // The TextInput's value will be controlled by its own state
143
- }
144
- // Clear previous debounce timer
145
- if (debounceTimerRef.current) {
146
- clearTimeout(debounceTimerRef.current);
147
- }
148
- // Use debounce for performance optimization
149
- debounceTimerRef.current = setTimeout(() => {
150
- // Emit query change event to decouple components and avoid context re-rendering
151
- emitQueryChange(searchQueryRef.current);
152
- }, 300); // Use 300ms debounce like the original
254
+ };
153
255
  }, []);
154
- // Calculate popup position
155
- const calculatePosition = useCallback(() => {
156
- if (!containerRef.current)
157
- return;
158
- containerRef.current.measureInWindow((x, y, width, height) => {
159
- const screenHeight = Dimensions.get('screen').height;
160
- const screenWidth = Dimensions.get('screen').width;
161
- let top = y + height;
162
- let left = x;
163
- let popupWidth = width;
164
- // Check if popup should appear above the input
165
- if (y + height + LIST_HEIGHT > screenHeight) {
166
- top = y - LIST_HEIGHT;
167
- }
168
- // Adjust horizontal position if needed
169
- if ((popUpViewStyle === null || popUpViewStyle === void 0 ? void 0 : popUpViewStyle.left) && (popUpViewStyle === null || popUpViewStyle === void 0 ? void 0 : popUpViewStyle.width)) {
170
- const leftPercent = parseFloat(String(popUpViewStyle.left).replace('%', '')) / 100;
171
- const widthPercent = parseFloat(String(popUpViewStyle.width).replace('%', '')) / 100;
172
- left = screenWidth * leftPercent;
173
- popupWidth = screenWidth * widthPercent;
174
- }
175
- setPopupPosition({ top, left, width: popupWidth });
176
- });
177
- }, [popUpViewStyle]);
178
- // Hide popup using RootView
179
- const hidePopup = useCallback(() => {
180
- var _a, _b;
181
- setIsVisible(false);
182
- // Reset search query
183
- searchQueryRef.current = '';
184
- if (textInputRef.current) {
185
- textInputRef.current.blur();
186
- (_b = (_a = textInputRef.current).clear) === null || _b === void 0 ? void 0 : _b.call(_a); // Clear the TextInput
256
+ useEffect(() => {
257
+ console.log('AutoPositionedPopup rootViews=', rootViews);
258
+ rootViewsRef.current = rootViews;
259
+ if (rootViews.length === 0) {
260
+ hasAddedRootView.current = false;
261
+ hasShownRootView.current = false;
262
+ ref_isFocus.current = false;
263
+ ref_isKeyboardFullyShown.current = false;
264
+ hasTriggeredFocus.current = false;
265
+ setState((prevState) => {
266
+ return Object.assign(Object.assign({}, prevState), { isFocus: false });
267
+ });
187
268
  }
188
- removeRootView(popupId.current, forceRemoveAllRootViewOnItemSelected, rootViewsRef.current);
189
- }, [removeRootView, forceRemoveAllRootViewOnItemSelected]);
190
- // Handle data updates from PopupList
191
- const handleDataUpdate = useCallback((newData) => {
192
- setData(newData);
193
- }, []);
194
- // Handle item selection
195
- const handleItemPress = useCallback((item) => {
196
- onItemSelected === null || onItemSelected === void 0 ? void 0 : onItemSelected(item);
197
- hidePopup();
198
- }, [onItemSelected, hidePopup]);
199
- // Show popup using RootView
200
- const showPopup = useCallback(() => {
201
- calculatePosition();
202
- setIsVisible(true);
203
- loadData(searchQueryRef.current);
204
- // Wait for position to be calculated
205
- setTimeout(() => {
206
- const popupComponent = (<TouchableOpacity style={{
207
- flex: 1,
208
- backgroundColor: 'rgba(0, 0, 0, 0.3)',
209
- }} activeOpacity={1} onPress={hidePopup}>
210
- <View style={{
211
- position: 'absolute',
212
- top: popupPosition.top,
213
- left: popupPosition.left,
214
- width: popupPosition.width,
215
- height: LIST_HEIGHT,
216
- backgroundColor: theme.colors.background,
217
- borderRadius: 8,
218
- shadowColor: '#000',
219
- shadowOffset: { width: 0, height: 2 },
220
- shadowOpacity: 0.25,
221
- shadowRadius: 3.84,
222
- elevation: 5,
223
- }}>
224
- {useTextInput && (<RNTextInput ref={textInputRef} style={[
225
- styles.inputStyle,
226
- {
227
- height: 40,
228
- borderBottomWidth: 1,
229
- borderBottomColor: theme.colors.border,
230
- paddingHorizontal: 12,
231
- color: theme.colors.text,
232
- },
233
- inputStyle,
234
- ]} placeholder={placeholder} placeholderTextColor={theme.colors.placeholderText} defaultValue={searchQueryRef.current} onChangeText={handleSearchChange} onSubmitEditing={(e) => {
235
- onSubmitEditing === null || onSubmitEditing === void 0 ? void 0 : onSubmitEditing(e);
236
- Keyboard.dismiss();
237
- }} returnKeyType="done" {...TextInputProps}/>)}
238
-
239
- <PopupList data={data} selectedItem={selectedItem} onItemPress={handleItemPress} renderItem={renderItem} keyExtractor={keyExtractor} theme={theme} rootViewsRef={rootViewsRef} fetchData={fetchData} localSearch={localSearch} pageSize={pageSize} onDataUpdate={handleDataUpdate} selectedItemBackgroundColor={selectedItemBackgroundColor}/>
240
- </View>
241
- </TouchableOpacity>);
242
- addRootView({
243
- id: popupId.current,
244
- style: {
245
- position: 'absolute',
246
- top: 0,
247
- left: 0,
248
- right: 0,
249
- bottom: 0,
250
- },
251
- component: popupComponent,
252
- useModal: true,
253
- onModalClose: hidePopup,
254
- centerDisplay: centerDisplay,
269
+ }, [rootViews]);
270
+ useEffect(() => {
271
+ var _a, _b;
272
+ console.log('AutoPositionedPopup useEffect tag=', tag);
273
+ console.log('AutoPositionedPopup useEffect selectedItem=', selectedItem);
274
+ console.log('AutoPositionedPopup useEffect state.selectedItem=', state.selectedItem);
275
+ 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)) {
276
+ console.log('AutoPositionedPopup useEffect selectedItem!=state.selectedItem');
277
+ setState((prevState) => {
278
+ return Object.assign(Object.assign({}, prevState), { selectedItem: selectedItem });
255
279
  });
256
- }, 100);
257
- }, [calculatePosition, loadData, popupPosition, useTextInput, placeholder, theme, inputStyle, TextInputProps, data, selectedItem, renderItem, keyExtractor, centerDisplay, addRootView, hidePopup, handleSearchChange, handleItemPress, LIST_HEIGHT, selectedItemBackgroundColor]);
258
- // Handle button press
259
- const handleButtonPress = useCallback(() => {
260
- if (AutoPositionedPopupBtnDisabled)
261
- return;
280
+ }
281
+ }, [selectedItem, state.selectedItem, tag]);
282
+ useEffect(() => {
283
+ var _a, _b;
284
+ console.log('AutoPositionedPopup useEffect tag=', tag);
285
+ console.log('AutoPositionedPopup useEffect state.isFocus=', state.isFocus);
286
+ console.log('AutoPositionedPopup useEffect isKeyboardFullyShown=', isKeyboardFullyShown);
287
+ console.log('AutoPositionedPopup useEffect ref_isFocus.current=', ref_isFocus.current);
288
+ console.log('AutoPositionedPopup useEffect ref_isKeyboardFullyShown.current=', ref_isKeyboardFullyShown.current);
289
+ console.log('AutoPositionedPopup useEffect useTextInput=', useTextInput);
290
+ console.log('AutoPositionedPopup useEffect TextInputProps=', TextInputProps);
291
+ console.log('AutoPositionedPopup useEffect hasAddedRootView.current=', hasAddedRootView.current);
292
+ console.log('AutoPositionedPopup useEffect hasShownRootView.current=', hasShownRootView.current);
262
293
  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);
294
+ if (isKeyboardFullyShown && hasAddedRootView.current && !hasShownRootView.current && state.isFocus) {
295
+ (_a = refAutoPositionedPopup.current) === null || _a === void 0 ? void 0 : _a.measureInWindow((x, y, width, height) => {
296
+ var _a;
297
+ console.log('AutoPositionedPopup measureInWindow x=', x, ' y=', y, ' width=', width, ' height=', height);
298
+ // SIMPLE CENTER-BASED POSITIONING STRATEGY
299
+ const screenHeight = Dimensions.get('screen').height;
300
+ const screenCenter = screenHeight / 2;
301
+ console.log('AutoPositionedPopup screenHeight=', screenHeight, ' screenCenter=', screenCenter, ' componentY=', y);
302
+ // Simple rule: if component Y > screen center, show popup above; otherwise show below
303
+ if (y > screenCenter) {
304
+ console.log('AutoPositionedPopup with keyboard: showing above (Y > center)');
305
+ ref_listPos.current = { x: x, y: y - listLayout.height, width: width };
306
+ }
307
+ else {
308
+ console.log('AutoPositionedPopup with keyboard: showing below (Y <= center)');
309
+ ref_listPos.current = { x: x, y: y + height, width: width };
310
+ }
311
+ console.log('AutoPositionedPopup ref_listPos.current=', ref_listPos.current);
312
+ setRootViewNativeStyle(tag, {
313
+ top: (_a = ref_listPos.current) === null || _a === void 0 ? void 0 : _a.y,
314
+ left: popUpViewStyle === null || popUpViewStyle === void 0 ? void 0 : popUpViewStyle.left,
315
+ width: popUpViewStyle === null || popUpViewStyle === void 0 ? void 0 : popUpViewStyle.width,
316
+ height: listLayout.height,
317
+ opacity: 1,
318
+ });
319
+ hasShownRootView.current = true;
320
+ });
321
+ }
322
+ else if (!isKeyboardFullyShown && ref_isFocus.current) {
323
+ console.log('AutoPositionedPopup isKeyboardFullyShown useEffect removeRootView tag=', tag, ' forceRemoveAllRootViewOnItemSelected=', forceRemoveAllRootViewOnItemSelected);
324
+ removeRootView(tag, forceRemoveAllRootViewOnItemSelected);
325
+ setState((prevState) => {
326
+ return Object.assign(Object.assign({}, prevState), { isFocus: false });
327
+ });
328
+ setSearchQuery('');
329
+ hasAddedRootView.current = false;
330
+ hasShownRootView.current = false;
331
+ }
269
332
  }
270
333
  else {
271
- showPopup();
334
+ if (state.isFocus) {
335
+ (_b = refAutoPositionedPopup.current) === null || _b === void 0 ? void 0 : _b.measureInWindow((x, y, width, height) => {
336
+ console.log('AutoPositionedPopup measureInWindow x=', x, ' y=', y, ' width=', width, ' height=', height);
337
+ // SIMPLE CENTER-BASED POSITIONING STRATEGY
338
+ const screenHeight = Dimensions.get('screen').height;
339
+ const screenCenter = screenHeight / 2;
340
+ console.log('AutoPositionedPopup screenHeight=', screenHeight, ' screenCenter=', screenCenter, ' componentY=', y);
341
+ // Simple rule: if component Y > screen center, show popup above; otherwise show below
342
+ if (y + insets.top > screenCenter) {
343
+ console.log('AutoPositionedPopup: showing above (Y > center)');
344
+ ref_listPos.current = { x: x, y: y - listLayout.height, width: width };
345
+ }
346
+ else {
347
+ console.log('AutoPositionedPopup: showing below (Y <= center)');
348
+ ref_listPos.current = { x: x, y: y + height + insets.top, width: width };
349
+ }
350
+ console.log('AutoPositionedPopup ref_listPos.current=', ref_listPos.current);
351
+ if (CustomPopView && CustomPopViewStyle) {
352
+ console.log('AutoPositionedPopup CustomPopViewStyle=', CustomPopViewStyle);
353
+ // Ensure CustomPopViewStyle.height is a number before using it in calculations
354
+ const customHeight = typeof CustomPopViewStyle.height === 'number' ? CustomPopViewStyle.height : listLayout.height;
355
+ // Apply same simple center-based strategy for CustomPopView
356
+ console.log('AutoPositionedPopup CustomPopView using center-based positioning, customHeight=', customHeight);
357
+ // Simple rule: if component Y > screen center, show popup above; otherwise show below
358
+ if (y > screenCenter) {
359
+ console.log('AutoPositionedPopup CustomPopView: showing above (Y > center), tag=', tag);
360
+ ref_listPos.current = { x: x, y: y - customHeight, width: width };
361
+ }
362
+ else {
363
+ console.log('AutoPositionedPopup CustomPopView: showing below (Y <= center), tag=', tag);
364
+ ref_listPos.current = { x: x, y: y + height, width: width };
365
+ }
366
+ const PopViewComponent = CustomPopView();
367
+ console.log('AutoPositionedPopup addRootView PopViewComponent=', PopViewComponent);
368
+ console.log('AutoPositionedPopup addRootView state.selectedItem=', state.selectedItem);
369
+ addRootView({
370
+ id: tag,
371
+ style: !centerDisplay
372
+ ? Object.assign({ top: ref_listPos.current.y, left: popUpViewStyle === null || popUpViewStyle === void 0 ? void 0 : popUpViewStyle.left, width: popUpViewStyle === null || popUpViewStyle === void 0 ? void 0 : popUpViewStyle.width, height: listLayout.height, opacity: 1 }, CustomPopViewStyle) : Object.assign({ width: popUpViewStyle === null || popUpViewStyle === void 0 ? void 0 : popUpViewStyle.width, height: listLayout.height }, CustomPopViewStyle),
373
+ component: <PopViewComponent selectedItem={state.selectedItem}></PopViewComponent>,
374
+ useModal: true,
375
+ onModalClose: () => {
376
+ console.log('AutoPositionedPopup onModalClose');
377
+ removeRootView(tag, forceRemoveAllRootViewOnItemSelected, rootViewsRef.current);
378
+ setState((prevState) => {
379
+ return Object.assign(Object.assign({}, prevState), { isFocus: false });
380
+ });
381
+ hasAddedRootView.current = false;
382
+ hasShownRootView.current = false;
383
+ hasTriggeredFocus.current = false;
384
+ setSearchQuery('');
385
+ },
386
+ centerDisplay,
387
+ });
388
+ }
389
+ else {
390
+ console.log('AutoPositionedPopup addRootView tag=', tag);
391
+ addRootView({
392
+ id: tag,
393
+ style: {
394
+ top: ref_listPos.current.y,
395
+ left: popUpViewStyle === null || popUpViewStyle === void 0 ? void 0 : popUpViewStyle.left,
396
+ width: popUpViewStyle === null || popUpViewStyle === void 0 ? void 0 : popUpViewStyle.width,
397
+ height: listLayout.height,
398
+ opacity: 1,
399
+ },
400
+ component: (<AutoPositionedPopupList tag={tag} updateState={updateState} fetchData={fetchData} pageSize={pageSize} renderItem={renderItem} selectedItem={state.selectedItem} localSearch={localSearch}/>),
401
+ useModal: true,
402
+ onModalClose: () => {
403
+ console.log('AutoPositionedPopup onModalClose tag=', tag);
404
+ removeRootView(tag, forceRemoveAllRootViewOnItemSelected, rootViewsRef.current);
405
+ setState((prevState) => {
406
+ return Object.assign({}, prevState);
407
+ });
408
+ setSearchQuery('');
409
+ },
410
+ });
411
+ }
412
+ });
413
+ }
414
+ }
415
+ if (isKeyboardFullyShown) {
416
+ ref_isFocus.current = state.isFocus;
417
+ if (isKeyboardFullyShown !== keyboardVisibleRef.current) {
418
+ keyboardVisibleRef.current = isKeyboardFullyShown;
419
+ if (isKeyboardFullyShown && textInputRef.current) {
420
+ if (ref_searchQuery.current) {
421
+ textInputRef.current.setNativeProps({ text: ref_searchQuery.current });
422
+ }
423
+ }
424
+ }
272
425
  }
273
- }, [AutoPositionedPopupBtnDisabled, useTextInput, showPopup]);
426
+ }, [insets,
427
+ isKeyboardFullyShown,
428
+ state.isFocus,
429
+ useTextInput,
430
+ CustomPopView,
431
+ CustomPopViewStyle,
432
+ forceRemoveAllRootViewOnItemSelected,
433
+ tag,
434
+ state.selectedItem,
435
+ ]);
274
436
  // Imperative handle for parent component access
275
437
  useImperativeHandle(parentRef, () => ({
276
438
  clearSelectedItem: () => {
277
- // Clear selection logic can be implemented here
278
- console.log('Clearing selected item for:', tag);
439
+ console.log('AutoPositionedPopup clearSelectedItem tag=', tag);
440
+ setState((prevState) => {
441
+ return Object.assign(Object.assign({}, prevState), { selectedItem: undefined });
442
+ });
279
443
  },
280
- showPopup,
281
- hidePopup,
282
- }), [tag, showPopup, hidePopup]);
283
- // Cleanup
284
- useEffect(() => {
285
- return () => {
286
- if (debounceTimerRef.current) {
287
- clearTimeout(debounceTimerRef.current);
288
- }
289
- };
290
- }, []);
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>);
444
+ }), []);
445
+ const updateState = (key, value) => {
446
+ console.log('AutoPositionedPopup updateState key=', key, ' value=', value);
447
+ setState((prevState) => (Object.assign(Object.assign({}, prevState), { [key]: value })));
448
+ if (key === 'selectedItem' && onItemSelected) {
449
+ onItemSelected(value);
450
+ console.log('AutoPositionedPopup updateState onItemSelected rootViewsRef.current=', rootViewsRef.current);
451
+ removeRootView(tag, forceRemoveAllRootViewOnItemSelected, rootViewsRef.current);
452
+ hasAddedRootView.current = false;
453
+ hasShownRootView.current = false;
454
+ hasTriggeredFocus.current = false;
455
+ setState((prevState) => {
456
+ return Object.assign(Object.assign({}, prevState), { isFocus: false });
457
+ });
458
+ setSearchQuery('');
459
+ }
460
+ };
461
+ // Render the component following project implementation
462
+ return useMemo(() => {
463
+ var _a;
464
+ console.log('AutoPositionedPopup render tag=', tag); // Now safe - circular dependency fixed
465
+ return (<CustomRow>
466
+ <View style={[styles.contain, style]} ref={refAutoPositionedPopup}>
467
+ {!state.isFocus || !useTextInput ? (<TouchableOpacity style={[styles.AutoPositionedPopupBtn, AutoPositionedPopupBtnStyle]} disabled={AutoPositionedPopupBtnDisabled} onPress={() => {
468
+ console.log('AutoPositionedPopup onPress tag=', tag);
469
+ console.log('AutoPositionedPopup onPress state.isFocus=', state.isFocus);
470
+ console.log('AutoPositionedPopup onPress useTextInput=', useTextInput);
471
+ console.log('AutoPositionedPopup onPress hasAddedRootView.current=', hasAddedRootView.current);
472
+ console.log('AutoPositionedPopup onPress hasShownRootView.current=', hasShownRootView.current);
473
+ console.log('AutoPositionedPopup onPress hasTriggeredFocus.current=', hasTriggeredFocus.current);
474
+ console.log('AutoPositionedPopup onPress state.selectedItem=', state.selectedItem);
475
+ setState((prevState) => {
476
+ return Object.assign(Object.assign({}, prevState), { isFocus: true });
477
+ });
478
+ if (!hasAddedRootView.current && useTextInput) {
479
+ hasAddedRootView.current = true;
480
+ hasShownRootView.current = false;
481
+ addRootView({
482
+ id: tag,
483
+ style: {
484
+ top: 0,
485
+ left: 0,
486
+ width: popUpViewStyle === null || popUpViewStyle === void 0 ? void 0 : popUpViewStyle.width,
487
+ height: listLayout.height,
488
+ opacity: 0,
489
+ },
490
+ component: (<AutoPositionedPopupList tag={tag} updateState={updateState} fetchData={fetchData} pageSize={pageSize} renderItem={renderItem} selectedItem={state.selectedItem} localSearch={localSearch}/>),
491
+ useModal: false,
492
+ });
493
+ }
494
+ }}>
495
+ {!btwChildren ? (<Text style={[
496
+ styles.searchQueryTxt,
497
+ state.selectedItem && { color: theme.colors.text },
498
+ labelStyle,
499
+ ]} numberOfLines={1} ellipsizeMode={'tail'}>
500
+ {((_a = state.selectedItem) === null || _a === void 0 ? void 0 : _a.title) || placeholder}
501
+ </Text>) : (btwChildren())}
502
+ </TouchableOpacity>) : (useTextInput &&
503
+ state.isFocus && (<RNTextInput ref={textInputRef} key="fixed-textinput-key" style={[
504
+ styles.inputStyle,
505
+ inputStyle,
506
+ ]} textAlign={TextInputProps['textAlign'] || 'left'} multiline={TextInputProps['multiline'] || false} numberOfLines={TextInputProps['numberOfLines'] || 1} onChangeText={(searchQuery) => {
507
+ ref_searchQuery.current = searchQuery;
508
+ console.log('AutoPositionedPopup onChangeText rootViews=', rootViews);
509
+ if (!localSearch) {
510
+ if (debounceTimerRef.current) {
511
+ clearTimeout(debounceTimerRef.current);
512
+ }
513
+ debounceTimerRef.current = setTimeout(() => {
514
+ emitQueryChange(ref_searchQuery.current);
515
+ }, 500);
516
+ }
517
+ else {
518
+ emitQueryChange(ref_searchQuery.current);
519
+ }
520
+ }} placeholderTextColor={theme.colors.placeholderText} placeholder={placeholder} onKeyPress={(e) => {
521
+ if (e.nativeEvent.key === 'Enter') {
522
+ Keyboard.dismiss();
523
+ }
524
+ }} 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={() => {
525
+ console.log('AutoPositionedPopup onFocus tag=', tag, ' selectedItem=', state.selectedItem, ' hasTriggeredFocus.current=', hasTriggeredFocus.current, ' textInputRef.current=', textInputRef.current, ' ref_searchQuery.current=', ref_searchQuery.current);
526
+ if (!hasTriggeredFocus.current) {
527
+ hasTriggeredFocus.current = true;
528
+ ref_isFocus.current = true;
529
+ if (state.selectedItem) {
530
+ ref_searchQuery.current = state.selectedItem.title;
531
+ }
532
+ if (textInputRef.current && ref_searchQuery.current) {
533
+ textInputRef.current.setNativeProps({
534
+ text: ref_searchQuery.current,
535
+ });
536
+ }
537
+ }
538
+ }} onBlur={() => {
539
+ console.log('AutoPositionedPopup onBlur tag=', tag, 'textInputRef.current=', textInputRef.current);
540
+ hasTriggeredFocus.current = false;
541
+ hasAddedRootView.current = false; // 重置 RootView 狀態
542
+ hasShownRootView.current = false;
543
+ ref_isFocus.current = false;
544
+ setState((prevState) => {
545
+ return Object.assign(Object.assign({}, prevState), { isFocus: false });
546
+ });
547
+ removeRootView(tag, forceRemoveAllRootViewOnItemSelected);
548
+ setSearchQuery('');
549
+ if (textInputRef.current) {
550
+ textInputRef.current.setNativeProps({ text: '' });
551
+ ref_searchQuery.current = '';
552
+ textInputRef.current.blur();
553
+ }
554
+ Keyboard.dismiss();
555
+ }} selectTextOnFocus={TextInputProps['selectTextOnFocus'] || false} onSubmitEditing={(e) => {
556
+ console.log('AutoPositionedPopup.tsx onSubmitEditing e.nativeEvent.text=', e.nativeEvent.text);
557
+ onSubmitEditing && onSubmitEditing(e);
558
+ }}/>))}
559
+ </View>
560
+ </CustomRow>);
561
+ }, [tag,
562
+ fetchData,
563
+ renderItem,
564
+ onItemSelected,
565
+ onSubmitEditing,
566
+ localSearch,
567
+ placeholder,
568
+ textAlign,
569
+ pageSize,
570
+ selectedItem,
571
+ CustomRow,
572
+ useTextInput,
573
+ btwChildren,
574
+ selectedItem,
575
+ keyExtractor,
576
+ AutoPositionedPopupBtnStyle,
577
+ CustomPopView,
578
+ CustomPopViewStyle,
579
+ forceRemoveAllRootViewOnItemSelected,
580
+ inputStyle,
581
+ TextInputProps,
582
+ state.isFocus,]);
305
583
  }));
306
584
  export default AutoPositionedPopup;
585
+ //# sourceMappingURL=AutoPositionedPopup.js.map