react-native-auto-positioned-popup 1.0.2
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/LICENSE +21 -0
- package/README.md +425 -0
- package/README_zh.md +425 -0
- package/lib/AutoPositionedPopup.d.ts +5 -0
- package/lib/AutoPositionedPopup.d.ts.map +1 -0
- package/lib/AutoPositionedPopup.js +306 -0
- package/lib/AutoPositionedPopup.style.d.ts +80 -0
- package/lib/AutoPositionedPopup.style.d.ts.map +1 -0
- package/lib/AutoPositionedPopup.style.js +79 -0
- package/lib/AutoPositionedPopupProps.d.ts +58 -0
- package/lib/AutoPositionedPopupProps.d.ts.map +1 -0
- package/lib/AutoPositionedPopupProps.js +1 -0
- package/lib/RootViewContext.d.ts +31 -0
- package/lib/RootViewContext.d.ts.map +1 -0
- package/lib/RootViewContext.js +136 -0
- package/lib/index.d.ts +6 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +7 -0
- package/package.json +82 -0
- package/src/AutoPositionedPopup.style.ts +80 -0
- package/src/AutoPositionedPopup.tsx +529 -0
- package/src/AutoPositionedPopupProps.ts +61 -0
- package/src/RootViewContext.tsx +186 -0
- package/src/index.ts +16 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import React, { ReactNode, createContext, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import { Pressable, View, ViewStyle } from 'react-native';
|
|
3
|
+
|
|
4
|
+
interface DynamicViewBase {
|
|
5
|
+
id: string;
|
|
6
|
+
style: ViewStyle;
|
|
7
|
+
component: ReactNode;
|
|
8
|
+
useModal?: boolean;
|
|
9
|
+
onModalClose?: () => void;
|
|
10
|
+
centerDisplay?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface RootViewContextType {
|
|
14
|
+
addRootView: (view: DynamicViewBase) => void;
|
|
15
|
+
setRootViewNativeStyle: (id: string, style: ViewStyle) => void;
|
|
16
|
+
updateRootView: (id: string, update: Partial<DynamicViewBase>) => void;
|
|
17
|
+
removeRootView: (id?: string, force?: boolean, _rootViews?: DynamicViewBase[]) => void;
|
|
18
|
+
rootViews: DynamicViewBase[];
|
|
19
|
+
searchQuery: string;
|
|
20
|
+
setSearchQuery: (searchQuery: string) => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface RootViewProviderProps {
|
|
24
|
+
children: ReactNode;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const RootViewContext = createContext<RootViewContextType | undefined>(undefined);
|
|
28
|
+
/**
|
|
29
|
+
* Dynamically add or remove views on the root view.
|
|
30
|
+
* @param children
|
|
31
|
+
* @constructor
|
|
32
|
+
*/
|
|
33
|
+
export const RootViewProvider: React.FC<RootViewProviderProps> = ({ children }) => {
|
|
34
|
+
const [rootViews, setRootViews] = useState<DynamicViewBase[]>([]);
|
|
35
|
+
const [searchQuery, setSearchQuery] = useState<string>('');
|
|
36
|
+
const viewRefs = useRef<Record<string, View>>({});
|
|
37
|
+
//監聽 rootViews
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
console.log('RootViewProvider rootViews changed:', rootViews);
|
|
40
|
+
}, [rootViews]);
|
|
41
|
+
const addRootView = (view: DynamicViewBase): void => {
|
|
42
|
+
// const id = `dynamic-view-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
43
|
+
const newView: DynamicViewBase = { ...view };
|
|
44
|
+
console.log('RootViewProvider addRootView rootViews=', rootViews);
|
|
45
|
+
console.log('RootViewProvider addRootView newView=', newView);
|
|
46
|
+
setRootViews((prev) => [...prev, newView]);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const updateRootView = (id: string, update: Partial<DynamicViewBase>): void => {
|
|
50
|
+
setRootViews((prev) => prev.map((view) => (view.id === id ? { ...view, ...update } : view)));
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const removeRootView = (id?: string, force?: boolean, _rootViews?: DynamicViewBase[]): void => {
|
|
54
|
+
console.log('RootViewProvider removeRootView id=', id);
|
|
55
|
+
console.log('RootViewProvider removeRootView force=', force);
|
|
56
|
+
console.log('RootViewProvider removeRootView rootViews=', rootViews);
|
|
57
|
+
console.log('RootViewProvider removeRootView _rootViews=', _rootViews);
|
|
58
|
+
|
|
59
|
+
// Ensure keyboard is dismissed when force removing all root views
|
|
60
|
+
if (force) {
|
|
61
|
+
// Dismiss keyboard first
|
|
62
|
+
if ((global as any).dismissKeyboard) {
|
|
63
|
+
(global as any).dismissKeyboard();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Small delay to ensure keyboard is dismissed before removing views
|
|
67
|
+
setTimeout(() => {
|
|
68
|
+
setRootViews((prev) => []);
|
|
69
|
+
console.log('RootViewProvider removeRootView setRootViews(prev => []) force=true');
|
|
70
|
+
}, 50);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (rootViews.length > 0 && id) {
|
|
74
|
+
setRootViews((prev) => prev.filter((view) => view.id !== id));
|
|
75
|
+
// else {
|
|
76
|
+
// console.log('RootViewProvider removeRootView setRootViews(prev => [])')
|
|
77
|
+
// setRootViews(prev => [])
|
|
78
|
+
// }
|
|
79
|
+
} else if (_rootViews && _rootViews.length > 0 && id) {
|
|
80
|
+
setRootViews((prev) => prev.filter((view) => view.id !== id));
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const setRootViewNativeStyle = (id: string, style: ViewStyle): void => {
|
|
85
|
+
const target = viewRefs.current[id];
|
|
86
|
+
if (target) {
|
|
87
|
+
// @ts-ignore - React Native setNativeProps
|
|
88
|
+
target.setNativeProps({ style });
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const contextValue = useMemo(
|
|
93
|
+
() => ({
|
|
94
|
+
addRootView,
|
|
95
|
+
setRootViewNativeStyle,
|
|
96
|
+
updateRootView,
|
|
97
|
+
removeRootView,
|
|
98
|
+
rootViews,
|
|
99
|
+
searchQuery,
|
|
100
|
+
setSearchQuery,
|
|
101
|
+
}),
|
|
102
|
+
[addRootView, setRootViewNativeStyle, updateRootView, removeRootView, rootViews, searchQuery, setSearchQuery]
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<RootViewContext.Provider value={contextValue}>
|
|
107
|
+
<>
|
|
108
|
+
{children}
|
|
109
|
+
{rootViews.map(
|
|
110
|
+
({ id, style, component, useModal, onModalClose, centerDisplay }: DynamicViewBase): React.JSX.Element => {
|
|
111
|
+
console.log('RootViewProvider rootViews.map id=', id);
|
|
112
|
+
console.log('RootViewProvider rootViews.map style=', style);
|
|
113
|
+
console.log('RootViewProvider rootViews.map component=', component);
|
|
114
|
+
console.log('RootViewProvider rootViews.map useModal=', useModal);
|
|
115
|
+
console.log('RootViewProvider rootViews.map centerDisplay=', centerDisplay);
|
|
116
|
+
return !useModal ? (
|
|
117
|
+
<View
|
|
118
|
+
key={id}
|
|
119
|
+
ref={(r) => {
|
|
120
|
+
if (r) viewRefs.current[id] = r;
|
|
121
|
+
}}
|
|
122
|
+
style={[style, { position: 'absolute' }]}
|
|
123
|
+
>
|
|
124
|
+
{component}
|
|
125
|
+
</View>
|
|
126
|
+
) : (
|
|
127
|
+
<Pressable
|
|
128
|
+
key={id}
|
|
129
|
+
style={[
|
|
130
|
+
{
|
|
131
|
+
flex: 1,
|
|
132
|
+
position: 'absolute',
|
|
133
|
+
width: '100%',
|
|
134
|
+
left: 0,
|
|
135
|
+
right: 0,
|
|
136
|
+
top: 0,
|
|
137
|
+
bottom: 0,
|
|
138
|
+
zIndex: 99999999999,
|
|
139
|
+
backgroundColor: 'rgba(0,0,0,0.5)',
|
|
140
|
+
},
|
|
141
|
+
centerDisplay && { justifyContent: 'center', alignItems: 'center' },
|
|
142
|
+
]}
|
|
143
|
+
onPress={() => {
|
|
144
|
+
console.log('RootViewProvider Pressable onPress rootViews=', rootViews);
|
|
145
|
+
removeRootView(id, true);
|
|
146
|
+
onModalClose && onModalClose();
|
|
147
|
+
}}
|
|
148
|
+
>
|
|
149
|
+
<View
|
|
150
|
+
ref={(r) => {
|
|
151
|
+
if (r) viewRefs.current[id] = r;
|
|
152
|
+
}}
|
|
153
|
+
style={[{ position: 'absolute' }, style]}
|
|
154
|
+
>
|
|
155
|
+
{component}
|
|
156
|
+
</View>
|
|
157
|
+
</Pressable>
|
|
158
|
+
);
|
|
159
|
+
// (<Modal
|
|
160
|
+
// animationType="none"
|
|
161
|
+
// transparent={false}
|
|
162
|
+
// visible={true}
|
|
163
|
+
// presentationStyle="overFullScreen" // iOS特定属性
|
|
164
|
+
// onRequestClose={() => {
|
|
165
|
+
// // Android 返回鍵按下時的回調
|
|
166
|
+
// onModalClose && onModalClose()
|
|
167
|
+
// }}
|
|
168
|
+
// key={id}
|
|
169
|
+
// >
|
|
170
|
+
// </Modal>)
|
|
171
|
+
}
|
|
172
|
+
)}
|
|
173
|
+
</>
|
|
174
|
+
</RootViewContext.Provider>
|
|
175
|
+
);
|
|
176
|
+
};
|
|
177
|
+
/*
|
|
178
|
+
const { addRootView, updateRootView, removeRootView ,searchQuery } = useRootView();
|
|
179
|
+
*/
|
|
180
|
+
export const useRootView = (): RootViewContextType => {
|
|
181
|
+
const context = useContext(RootViewContext);
|
|
182
|
+
if (!context) {
|
|
183
|
+
throw new Error('useRootView must be used within a RootViewProvider');
|
|
184
|
+
}
|
|
185
|
+
return context;
|
|
186
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Main component export
|
|
2
|
+
export { default as AutoPositionedPopup } from './AutoPositionedPopup';
|
|
3
|
+
export { default } from './AutoPositionedPopup';
|
|
4
|
+
|
|
5
|
+
// Type exports
|
|
6
|
+
export type {
|
|
7
|
+
AutoPositionedPopupProps,
|
|
8
|
+
SelectedItem,
|
|
9
|
+
Data,
|
|
10
|
+
} from './AutoPositionedPopupProps';
|
|
11
|
+
|
|
12
|
+
// Style exports (optional, for customization)
|
|
13
|
+
export { default as AutoPositionedPopupStyles } from './AutoPositionedPopup.style';
|
|
14
|
+
|
|
15
|
+
// RootView exports
|
|
16
|
+
export { RootViewProvider, useRootView } from './RootViewContext';
|