@@ -9,12 +9,13 @@ type Component<P> = React.Component<P> | React.ComponentType<P>;
99
1010type ComponentProps < C extends Component < any > > = C extends Component < infer P > ? P : never ;
1111
12+ type PortalNodeElementType = 'html' | 'svg' ;
1213type PortalNodeElement = HTMLElement | SVGElement ;
1314
1415export interface PortalNode < C extends Component < any > = Component < any > > {
1516 // The dom element used for the React portal. Created on portal mount.
16- element : PortalNodeElement | null ,
17- onReady : ( ( ) => void ) | null ,
17+ element : PortalNodeElement ,
18+ elementType : PortalNodeElementType ,
1819 // Used by the out portal to send props back to the real element
1920 // Hooked by InPortal to become a state update (and thus rerender)
2021 setPortalProps ( p : ComponentProps < C > ) : void ;
@@ -33,15 +34,26 @@ interface InPortalProps {
3334 children : React . ReactNode ;
3435}
3536
36- export const createPortalNode = < C extends Component < any > > ( ) : PortalNode < C > => {
37+ export const createPortalNode = < C extends Component < any > > ( elementType : PortalNodeElementType ) : PortalNode < C > => {
3738 let initialProps = { } as ComponentProps < C > ;
3839
3940 let parent : Node | undefined ;
4041 let lastPlaceholder : Node | undefined ;
4142
43+ const isHtmlPortal = elementType === 'html' ;
44+ const isSvgPortal = elementType === 'svg'
45+ let element ;
46+ if ( isHtmlPortal ) {
47+ element = document . createElement ( 'div' ) ;
48+ } else if ( isSvgPortal ) {
49+ element = document . createElementNS ( SVG_NAMESPACE , 'g' ) ;
50+ } else {
51+ throw new Error ( `Invalid element type "${ elementType } " for createPortalNode: must be "html" or "svg".` )
52+ }
53+
4254 const portalNode : PortalNode = {
43- element : null ,
44- onReady : null ,
55+ element,
56+ elementType ,
4557 setPortalProps : ( props : ComponentProps < C > ) => {
4658 initialProps = props ;
4759 } ,
@@ -55,13 +67,13 @@ export const createPortalNode = <C extends Component<any>>(): PortalNode<C> => {
5567 }
5668 portalNode . unmount ( ) ;
5769
58- // To support SVG and other non-html elements, the portalNode's element needs to be
59- // created with the correct namespace.
60- if ( ! portalNode . element || portalNode . element . tagName !== newParent . tagName ) {
61- if ( newParent instanceof SVGElement ) {
62- portalNode . element = document . createElementNS ( SVG_NAMESPACE , newParent . tagName ) ;
63- } else {
64- portalNode . element = document . createElement ( newParent . tagName ) ;
70+ // To support SVG and other non-html elements, the portalNode's elementType needs to match
71+ // the elementType it's being rendered into
72+ if ( newParent !== parent ) {
73+ console . log ( ' newParent: ' , newParent )
74+ if ( ( isHtmlPortal && ! ( newParent instanceof HTMLElement ) ) ||
75+ ( isSvgPortal && ! ( newParent instanceof SVGElement ) ) ) {
76+ throw new Error ( `Invalid element type for portal: " ${ elementType } " portalNodes must be used with ${ elementType } elements.` )
6577 }
6678 }
6779
@@ -95,13 +107,12 @@ export const createPortalNode = <C extends Component<any>>(): PortalNode<C> => {
95107 return portalNode ;
96108} ;
97109
98- export class InPortal extends React . PureComponent < InPortalProps , { nodeProps : { } , onReadyFired : boolean } > {
110+ export class InPortal extends React . PureComponent < InPortalProps , { nodeProps : { } } > {
99111
100112 constructor ( props : InPortalProps ) {
101113 super ( props ) ;
102114 this . state = {
103115 nodeProps : this . props . node . getInitialPortalProps ( ) ,
104- onReadyFired : false ,
105116 } ;
106117 }
107118
@@ -125,14 +136,6 @@ export class InPortal extends React.PureComponent<InPortalProps, { nodeProps: {}
125136 render ( ) {
126137 const { children, node } = this . props ;
127138
128- if ( ! node . element ) {
129- // The OutPortal has not yet determined whether or not we're in a special namespace like SVG:
130- // delay our rendering for now, and attach an onReady callback which the OutPortal can use to
131- // trigger a rerender once we're ready
132- node . onReady = ( ) => this . setState ( { onReadyFired : true } )
133- return null ;
134- }
135-
136139 return ReactDOM . createPortal (
137140 React . Children . map ( children , ( child ) => {
138141 if ( ! React . isValidElement ( child ) ) return child ;
@@ -157,15 +160,6 @@ export class OutPortal<C extends Component<any>> extends React.PureComponent<Out
157160 this . passPropsThroughPortal ( ) ;
158161 }
159162
160- maybeTriggerOnReady ( ) {
161- if ( this . props . node . onReady ) {
162- // There's an InPortal which is waiting on this OutPortal:
163- // rerender it now that the OutPortal and portalNode are ready
164- this . props . node . onReady ( ) ;
165- this . props . node . onReady = null ;
166- }
167- }
168-
169163 passPropsThroughPortal ( ) {
170164 const propsForTarget = Object . assign ( { } , this . props , { node : undefined } ) ;
171165 this . props . node . setPortalProps ( propsForTarget ) ;
@@ -179,7 +173,6 @@ export class OutPortal<C extends Component<any>> extends React.PureComponent<Out
179173 const parent = placeholder . parentNode ! ;
180174 node . mount ( parent , placeholder ) ;
181175 this . passPropsThroughPortal ( ) ;
182- this . maybeTriggerOnReady ( ) ;
183176 }
184177
185178 componentDidUpdate ( ) {
@@ -197,7 +190,6 @@ export class OutPortal<C extends Component<any>> extends React.PureComponent<Out
197190 const parent = placeholder . parentNode ! ;
198191 node . mount ( parent , placeholder ) ;
199192 this . passPropsThroughPortal ( ) ;
200- this . maybeTriggerOnReady ( ) ;
201193 }
202194
203195 componentWillUnmount ( ) {
@@ -208,6 +200,7 @@ export class OutPortal<C extends Component<any>> extends React.PureComponent<Out
208200 render ( ) {
209201 // Render a placeholder to the DOM, so we can get a reference into
210202 // our location in the DOM, and swap it out for the portaled node.
203+ // A <div> placeholder works fine even for SVG.
211204 return < div ref = { this . placeholderNode } /> ;
212205 }
213206
0 commit comments