11import * as React from 'react' ;
22import * 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
612const SVG_NAMESPACE = 'http://www.w3.org/2000/svg' ;
@@ -9,13 +15,7 @@ type Component<P> = React.Component<P> | React.ComponentType<P>;
915
1016type 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+
110118class 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
149157type OutPortalProps < C extends Component < any > > = {
150- node : PortalNode < C >
158+ node : AnyPortalNode < C >
151159} & Partial < ComponentProps < C > > ;
152160
153161class 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
212220export const html = {
0 commit comments