1 | import * as React from 'react';
|
2 | import { View, StyleSheet } from 'react-native';
|
3 | import PortalManager from './PortalManager';
|
4 |
|
5 | type Props = {
|
6 | children: React.ReactNode;
|
7 | };
|
8 |
|
9 | type Operation =
|
10 | | { type: 'mount'; key: number; children: React.ReactNode }
|
11 | | { type: 'update'; key: number; children: React.ReactNode }
|
12 | | { type: 'unmount'; key: number };
|
13 |
|
14 | export type PortalMethods = {
|
15 | mount: (children: React.ReactNode) => number;
|
16 | update: (key: number, children: React.ReactNode) => void;
|
17 | unmount: (key: number) => void;
|
18 | };
|
19 |
|
20 | export const PortalContext = React.createContext<PortalMethods>(null as any);
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 | export default class PortalHost extends React.Component<Props> {
|
45 | static displayName = 'Portal.Host';
|
46 |
|
47 | componentDidMount() {
|
48 | const manager = this.manager;
|
49 | const queue = this.queue;
|
50 |
|
51 | while (queue.length && manager) {
|
52 | const action = queue.pop();
|
53 | if (action) {
|
54 |
|
55 | switch (action.type) {
|
56 | case 'mount':
|
57 | manager.mount(action.key, action.children);
|
58 | break;
|
59 | case 'update':
|
60 | manager.update(action.key, action.children);
|
61 | break;
|
62 | case 'unmount':
|
63 | manager.unmount(action.key);
|
64 | break;
|
65 | }
|
66 | }
|
67 | }
|
68 | }
|
69 |
|
70 | private setManager = (manager: PortalManager | undefined | null) => {
|
71 | this.manager = manager;
|
72 | };
|
73 |
|
74 | private mount = (children: React.ReactNode) => {
|
75 | const key = this.nextKey++;
|
76 |
|
77 | if (this.manager) {
|
78 | this.manager.mount(key, children);
|
79 | } else {
|
80 | this.queue.push({ type: 'mount', key, children });
|
81 | }
|
82 |
|
83 | return key;
|
84 | };
|
85 |
|
86 | private update = (key: number, children: React.ReactNode) => {
|
87 | if (this.manager) {
|
88 | this.manager.update(key, children);
|
89 | } else {
|
90 | const op: Operation = { type: 'mount', key, children };
|
91 | const index = this.queue.findIndex(
|
92 | (o) => o.type === 'mount' || (o.type === 'update' && o.key === key)
|
93 | );
|
94 |
|
95 | if (index > -1) {
|
96 | this.queue[index] = op;
|
97 | } else {
|
98 | this.queue.push(op as Operation);
|
99 | }
|
100 | }
|
101 | };
|
102 |
|
103 | private unmount = (key: number) => {
|
104 | if (this.manager) {
|
105 | this.manager.unmount(key);
|
106 | } else {
|
107 | this.queue.push({ type: 'unmount', key });
|
108 | }
|
109 | };
|
110 |
|
111 | private nextKey = 0;
|
112 | private queue: Operation[] = [];
|
113 | private manager: PortalManager | null | undefined;
|
114 |
|
115 | render() {
|
116 | return (
|
117 | <PortalContext.Provider
|
118 | value={{
|
119 | mount: this.mount,
|
120 | update: this.update,
|
121 | unmount: this.unmount,
|
122 | }}
|
123 | >
|
124 | {}
|
125 | <View
|
126 | style={styles.container}
|
127 | collapsable={false}
|
128 | pointerEvents="box-none"
|
129 | >
|
130 | {this.props.children}
|
131 | </View>
|
132 | <PortalManager ref={this.setManager} />
|
133 | </PortalContext.Provider>
|
134 | );
|
135 | }
|
136 | }
|
137 |
|
138 | const styles = StyleSheet.create({
|
139 | container: {
|
140 | flex: 1,
|
141 | },
|
142 | });
|