11import * as React from 'react' ;
22import * as ReactDOM from 'react-dom' ;
33
4+ console . log ( 'React = ' , React )
5+ console . log ( 'ReactDOM = ' , ReactDOM )
6+
7+ // These namespaces come from react-dom, which does not export them publicly
8+ // https://github.com/facebook/react/blob/b87aabdfe1b7461e7331abb3601d9e6bb27544bc/packages/react-dom/src/shared/DOMNamespaces.js#L8-L16
9+ const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml' ;
10+ const MATH_NAMESPACE = 'http://www.w3.org/1998/Math/MathML' ;
11+ const SVG_NAMESPACE = 'http://www.w3.org/2000/svg' ;
12+ export const Namespaces = {
13+ html : HTML_NAMESPACE ,
14+ mathml : MATH_NAMESPACE ,
15+ svg : SVG_NAMESPACE ,
16+ } ;
17+
418type Component < P > = React . Component < P > | React . ComponentType < P > ;
519
620type ComponentProps < C extends Component < any > > = C extends Component < infer P > ? P : never ;
721
8- export interface PortalNode < C extends Component < any > = Component < any > > extends HTMLElement {
22+ type PortalNodeElement = HTMLElement | SVGElement ;
23+
24+ export interface PortalNode < C extends Component < any > = Component < any > > {
25+ element : PortalNodeElement ,
926 // Used by the out portal to send props back to the real element
1027 // Hooked by InPortal to become a state update (and thus rerender)
1128 setPortalProps ( p : ComponentProps < C > ) : void ;
@@ -24,38 +41,61 @@ interface InPortalProps {
2441 children : React . ReactNode ;
2542}
2643
27- export const createPortalNode = < C extends Component < any > > ( tagName : string = 'div' ) : PortalNode < C > => {
44+ export const createPortalNode = < C extends Component < any > > ( ) : PortalNode < C > => {
2845 let initialProps = { } as ComponentProps < C > ;
2946
3047 let parent : Node | undefined ;
3148 let lastPlaceholder : Node | undefined ;
3249
33- const portalNode = Object . assign ( document . createElement ( tagName ) , {
50+ const portalNode : PortalNode = {
51+ // @ts -ignore
52+ element : null ,
53+ // element: document.createElement('div'),
3454 setPortalProps : ( props : ComponentProps < C > ) => {
3555 initialProps = props ;
3656 } ,
3757 getInitialPortalProps : ( ) => {
3858 return initialProps ;
3959 } ,
40- mount : ( newParent : Node , newPlaceholder : Node ) => {
60+ mount : ( newParent : PortalNodeElement , newPlaceholder : PortalNodeElement ) => {
4161 if ( newPlaceholder === lastPlaceholder ) {
4262 // Already mounted - noop.
4363 return ;
4464 }
4565 portalNode . unmount ( ) ;
4666
47- // If either the PortalNode or its content is an SVG element, we need to treat it differently
48- if ( portalNode instanceof SVGElement || newPlaceholder instanceof SVGElement ) {
49- // replaceChild does not work well for SVG elements: it will rearrange the dom
50- // properly but does not render as expected.
51- ( newPlaceholder as HTMLElement ) . outerHTML = portalNode . innerHTML ;
52- } else {
53- newParent . replaceChild (
54- portalNode ,
55- newPlaceholder
56- ) ;
67+ console . log ( 'portalNode.mount()' , {
68+ newParent, newPlaceholder,
69+ parent, lastPlaceholder,
70+ portalNode,
71+ } ) ;
72+
73+ if ( ! portalNode . element ) {
74+ if ( newParent instanceof SVGElement ) {
75+ portalNode . element = document . createElementNS ( SVG_NAMESPACE , newParent . tagName ) ;
76+ } else {
77+ portalNode . element = document . createElement ( newParent . tagName ) ;
78+ }
79+
80+ console . log ( 'CREATED portalNode.element!!!' , portalNode . element ) ;
81+
82+ } else if ( newParent . tagName !== portalNode . element . tagName ) {
83+ const oldElement = portalNode . element ;
84+
85+ if ( newParent instanceof SVGElement ) {
86+ portalNode . element = document . createElementNS ( SVG_NAMESPACE , newParent . tagName ) ;
87+ } else {
88+ portalNode . element = document . createElement ( newParent . tagName ) ;
89+ }
90+
91+ console . log ( 'REPLACED portalNode.element!!!' , oldElement , ' -> ' , portalNode . element )
5792 }
5893
94+ newParent . replaceChild (
95+ portalNode . element ,
96+ newPlaceholder
97+ ) ;
98+
5999 parent = newParent ;
60100 lastPlaceholder = newPlaceholder ;
61101 } ,
@@ -67,21 +107,21 @@ export const createPortalNode = <C extends Component<any>>(tagName: string = 'di
67107 }
68108
69109 if ( parent && lastPlaceholder ) {
70- // If either the PortalNode or its content is an SVG element, we need to treat it differently
71- if ( portalNode instanceof SVGElement || lastPlaceholder instanceof SVGElement ) {
72- ( portalNode as HTMLElement ) . innerHTML = '' ;
73- } else {
110+ if ( portalNode . element ) {
74111 parent . replaceChild (
75112 lastPlaceholder ,
76- portalNode
113+ portalNode . element
77114 ) ;
78- }
79115
80- parent = undefined ;
81- lastPlaceholder = undefined ;
116+ parent = undefined ;
117+ lastPlaceholder = undefined ;
118+ } else {
119+ // Panic!
120+ throw new Error ( 'No element available, in portalNode.mount!' ) ;
121+ }
82122 }
83123 }
84- } ) ;
124+ } ;
85125
86126 return portalNode ;
87127} ;
@@ -113,14 +153,19 @@ export class InPortal extends React.PureComponent<InPortalProps, { nodeProps: {}
113153 }
114154
115155 render ( ) {
156+ console . log ( 'InPortal.render()' , this ) ;
116157 const { children, node } = this . props ;
158+ console . log ( 'InPortal.render()...node = ' , node ) ;
159+ console . log ( 'InPortal.render()...node.element = ' , node . element ) ;
160+
161+ if ( ! node . element ) return null ;
117162
118163 return ReactDOM . createPortal (
119164 React . Children . map ( children , ( child ) => {
120165 if ( ! React . isValidElement ( child ) ) return child ;
121166 return React . cloneElement ( child , this . state . nodeProps )
122167 } ) ,
123- node
168+ node . element
124169 ) ;
125170 }
126171}
@@ -177,12 +222,11 @@ export class OutPortal<C extends Component<any>> extends React.PureComponent<Out
177222 }
178223
179224 render ( ) {
180- const { tagName } = this . props . node ;
181- const NodeTagName = tagName . toLowerCase ( ) ;
225+ console . log ( 'OutPortal.render()' , this ) ;
182226
183227 // Render a placeholder to the DOM, so we can get a reference into
184228 // our location in the DOM, and swap it out for the portaled node.
185- return < NodeTagName ref = { this . placeholderNode } /> ;
229+ return < div ref = { this . placeholderNode } /> ;
186230 }
187231
188232}
0 commit comments