Skip to content

Commit 7dd15ed

Browse files
committed
Clean up and fix typings
1 parent 15760ee commit 7dd15ed

File tree

1 file changed

+39
-31
lines changed

1 file changed

+39
-31
lines changed

src/index.tsx

Lines changed: 39 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import * as React from 'react';
22
import * as ReactDOM from 'react-dom';
33

4+
// Internally, the portalNode must be for either HTML or SVG elements
5+
const ELEMENT_TYPE_HTML = 'html';
6+
const ELEMENT_TYPE_SVG = 'svg';
7+
8+
type ANY_ELEMENT_TYPE = typeof ELEMENT_TYPE_HTML | typeof ELEMENT_TYPE_SVG;
9+
410
// ReactDOM can handle several different namespaces, but they're not exported publicly
511
// https://github.com/facebook/react/blob/b87aabdfe1b7461e7331abb3601d9e6bb27544bc/packages/react-dom/src/shared/DOMNamespaces.js#L8-L10
612
const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
@@ -9,13 +15,7 @@ type Component<P> = React.Component<P> | React.ComponentType<P>;
915

1016
type ComponentProps<C extends Component<any>> = C extends Component<infer P> ? P : never;
1117

12-
type PortalNodeElementType = 'html' | 'svg';
13-
type PortalNodeElement = HTMLElement | SVGElement;
14-
15-
export interface PortalNode<C extends Component<any> = Component<any>> {
16-
// The dom element used for the React portal. Created on portal mount.
17-
element: PortalNodeElement,
18-
elementType: PortalNodeElementType,
18+
interface PortalNodeBase<C extends Component<any>> {
1919
// Used by the out portal to send props back to the real element
2020
// Hooked by InPortal to become a state update (and thus rerender)
2121
setPortalProps(p: ComponentProps<C>): void;
@@ -28,31 +28,34 @@ export interface PortalNode<C extends Component<any> = Component<any>> {
2828
// latest placeholder we replaced. This avoids some race conditions.
2929
unmount(expectedPlaceholder?: Node): void;
3030
}
31-
32-
interface InPortalProps {
33-
node: PortalNode;
34-
children: React.ReactNode;
31+
export interface HtmlPortalNode<C extends Component<any> = Component<any>> extends PortalNodeBase<C> {
32+
element: HTMLElement;
33+
elementType: typeof ELEMENT_TYPE_HTML;
34+
}
35+
export interface SvgPortalNode<C extends Component<any> = Component<any>> extends PortalNodeBase<C> {
36+
element: SVGElement;
37+
elementType: typeof ELEMENT_TYPE_SVG;
3538
}
39+
type AnyPortalNode<C extends Component<any> = Component<any>> = HtmlPortalNode<C> | SvgPortalNode<C>;
40+
3641

3742
// This is the internal implementation: the public entry points set elementType to an appropriate value
38-
const createPortalNode = <C extends Component<any>>(elementType: PortalNodeElementType): PortalNode<C> => {
43+
const createPortalNode = <C extends Component<any>>(elementType: ANY_ELEMENT_TYPE): AnyPortalNode<C> => {
3944
let initialProps = {} as ComponentProps<C>;
4045

4146
let parent: Node | undefined;
4247
let lastPlaceholder: Node | undefined;
4348

44-
const isHtmlPortal = elementType==='html';
45-
const isSvgPortal=elementType==='svg'
4649
let element;
47-
if (isHtmlPortal) {
50+
if (elementType === ELEMENT_TYPE_HTML) {
4851
element= document.createElement('div');
49-
} else if (isSvgPortal){
52+
} else if (elementType === ELEMENT_TYPE_SVG){
5053
element= document.createElementNS(SVG_NAMESPACE, 'g');
5154
} else {
5255
throw new Error(`Invalid element type "${elementType}" for createPortalNode: must be "html" or "svg".`)
5356
}
5457

55-
const portalNode: PortalNode = {
58+
const portalNode: AnyPortalNode<C> = {
5659
element,
5760
elementType,
5861
setPortalProps: (props: ComponentProps<C>) => {
@@ -61,7 +64,7 @@ const createPortalNode = <C extends Component<any>>(elementType: PortalNodeEleme
6164
getInitialPortalProps: () => {
6265
return initialProps;
6366
},
64-
mount: (newParent: PortalNodeElement, newPlaceholder: PortalNodeElement) => {
67+
mount: (newParent: HTMLElement, newPlaceholder: HTMLElement) => {
6568
if (newPlaceholder === lastPlaceholder) {
6669
// Already mounted - noop.
6770
return;
@@ -71,15 +74,15 @@ const createPortalNode = <C extends Component<any>>(elementType: PortalNodeEleme
7174
// To support SVG and other non-html elements, the portalNode's elementType needs to match
7275
// the elementType it's being rendered into
7376
if (newParent !== parent) {
74-
if ((isHtmlPortal && !(newParent instanceof HTMLElement)) ||
75-
(isSvgPortal && !(newParent instanceof SVGElement))) {
77+
if ((elementType === ELEMENT_TYPE_HTML && !(newParent instanceof HTMLElement)) ||
78+
(elementType === ELEMENT_TYPE_SVG && !(newParent instanceof SVGElement))) {
7679
throw new Error(`Invalid element type for portal: "${elementType}" portalNodes must be used with ${elementType} elements.`)
7780
}
7881
}
7982

8083
newParent.replaceChild(
8184
portalNode.element,
82-
newPlaceholder
85+
newPlaceholder,
8386
);
8487

8588
parent = newParent;
@@ -95,18 +98,23 @@ const createPortalNode = <C extends Component<any>>(elementType: PortalNodeEleme
9598
if (parent && lastPlaceholder) {
9699
parent.replaceChild(
97100
lastPlaceholder,
98-
portalNode.element as PortalNodeElement
101+
portalNode.element,
99102
);
100103

101104
parent = undefined;
102105
lastPlaceholder = undefined;
103106
}
104107
}
105-
};
108+
} as AnyPortalNode<C>;
106109

107110
return portalNode;
108111
};
109112

113+
interface InPortalProps {
114+
node: AnyPortalNode;
115+
children: React.ReactNode;
116+
}
117+
110118
class InPortal extends React.PureComponent<InPortalProps, { nodeProps: {} }> {
111119

112120
constructor(props: InPortalProps) {
@@ -141,19 +149,19 @@ class InPortal extends React.PureComponent<InPortalProps, { nodeProps: {} }> {
141149
if (!React.isValidElement(child)) return child;
142150
return React.cloneElement(child, this.state.nodeProps)
143151
}),
144-
node.element as PortalNodeElement
152+
node.element
145153
);
146154
}
147155
}
148156

149157
type OutPortalProps<C extends Component<any>> = {
150-
node: PortalNode<C>
158+
node: AnyPortalNode<C>
151159
} & Partial<ComponentProps<C>>;
152160

153161
class OutPortal<C extends Component<any>> extends React.PureComponent<OutPortalProps<C>> {
154162

155163
private placeholderNode = React.createRef<HTMLDivElement>();
156-
private currentPortalNode?: PortalNode<C>;
164+
private currentPortalNode?: AnyPortalNode<C>;
157165

158166
constructor(props: OutPortalProps<C>) {
159167
super(props);
@@ -166,7 +174,7 @@ class OutPortal<C extends Component<any>> extends React.PureComponent<OutPortalP
166174
}
167175

168176
componentDidMount() {
169-
const node = this.props.node as PortalNode<C>;
177+
const node = this.props.node as AnyPortalNode<C>;
170178
this.currentPortalNode = node;
171179

172180
const placeholder = this.placeholderNode.current!;
@@ -178,7 +186,7 @@ class OutPortal<C extends Component<any>> extends React.PureComponent<OutPortalP
178186
componentDidUpdate() {
179187
// We re-mount on update, just in case we were unmounted (e.g. by
180188
// a second OutPortal, which has now been removed)
181-
const node = this.props.node as PortalNode<C>;
189+
const node = this.props.node as AnyPortalNode<C>;
182190

183191
// If we're switching portal nodes, we need to clean up the current one first.
184192
if (this.currentPortalNode && node !== this.currentPortalNode) {
@@ -193,7 +201,7 @@ class OutPortal<C extends Component<any>> extends React.PureComponent<OutPortalP
193201
}
194202

195203
componentWillUnmount() {
196-
const node = this.props.node as PortalNode<C>;
204+
const node = this.props.node as AnyPortalNode<C>;
197205
node.unmount(this.placeholderNode.current!);
198206
}
199207

@@ -205,8 +213,8 @@ class OutPortal<C extends Component<any>> extends React.PureComponent<OutPortalP
205213
}
206214
}
207215

208-
const createHtmlPortalNode = createPortalNode.bind(null, 'html');
209-
const createSvgPortalNode = createPortalNode.bind(null, 'svg')
216+
const createHtmlPortalNode = createPortalNode.bind(null, ELEMENT_TYPE_HTML) as () => HtmlPortalNode;
217+
const createSvgPortalNode = createPortalNode.bind(null, ELEMENT_TYPE_SVG) as () => SvgPortalNode;
210218

211219
// Option A: Export a self-contained namespace for each type, including Portal components
212220
export const html = {

0 commit comments

Comments
 (0)