@@ -2,22 +2,19 @@ import * as React from 'react';
22import * as ReactDOM from 'react-dom' ;
33
44// Internally, the portalNode must be for either HTML or SVG elements
5- const ELEMENT_TYPE_HTML_BLOCK = 'div' ;
6- const ELEMENT_TYPE_HTML_INLINE = 'span' ;
5+ const ELEMENT_TYPE_HTML = 'html' ;
76const ELEMENT_TYPE_SVG = 'svg' ;
87
9- type ANY_ELEMENT_TYPE = typeof ELEMENT_TYPE_HTML_BLOCK | typeof ELEMENT_TYPE_HTML_INLINE | typeof ELEMENT_TYPE_SVG ;
10-
118type BaseOptions = {
129 attributes ?: { [ key : string ] : string } ;
1310} ;
1411
1512type HtmlOptions = BaseOptions & {
16- containerElement ?: typeof ELEMENT_TYPE_HTML_BLOCK | typeof ELEMENT_TYPE_HTML_INLINE ;
13+ containerElement ?: keyof HTMLElementTagNameMap ;
1714} ;
1815
1916type SvgOptions = BaseOptions & {
20- containerElement ?: typeof ELEMENT_TYPE_SVG ;
17+ containerElement ?: keyof SVGElementTagNameMap ;
2118} ;
2219
2320type Options = HtmlOptions | SvgOptions ;
@@ -45,7 +42,7 @@ interface PortalNodeBase<C extends Component<any>> {
4542}
4643export interface HtmlPortalNode < C extends Component < any > = Component < any > > extends PortalNodeBase < C > {
4744 element : HTMLElement ;
48- elementType : typeof ELEMENT_TYPE_HTML_BLOCK | typeof ELEMENT_TYPE_HTML_INLINE ;
45+ elementType : typeof ELEMENT_TYPE_HTML ;
4946}
5047export interface SvgPortalNode < C extends Component < any > = Component < any > > extends PortalNodeBase < C > {
5148 element : SVGElement ;
@@ -54,15 +51,14 @@ export interface SvgPortalNode<C extends Component<any> = Component<any>> extend
5451type AnyPortalNode < C extends Component < any > = Component < any > > = HtmlPortalNode < C > | SvgPortalNode < C > ;
5552
5653
57- const validateElementType = ( domElement : Element , elementType : ANY_ELEMENT_TYPE ) => {
54+ const validateElementType = ( domElement : Element , elementType : typeof ELEMENT_TYPE_HTML | typeof ELEMENT_TYPE_SVG ) => {
5855 const ownerDocument = ( domElement . ownerDocument ?? document ) as any ;
5956 // Cast document to `any` because Typescript doesn't know about the legacy `Document.parentWindow` field, and also
6057 // doesn't believe `Window.HTMLElement`/`Window.SVGElement` can be used in instanceof tests.
6158 const ownerWindow = ownerDocument . defaultView ?? ownerDocument . parentWindow ?? window ; // `parentWindow` for IE8 and earlier
6259
6360 switch ( elementType ) {
64- case ELEMENT_TYPE_HTML_BLOCK :
65- case ELEMENT_TYPE_HTML_INLINE :
61+ case ELEMENT_TYPE_HTML :
6662 return domElement instanceof ownerWindow . HTMLElement ;
6763 case ELEMENT_TYPE_SVG :
6864 return domElement instanceof ownerWindow . SVGElement ;
@@ -73,10 +69,9 @@ const validateElementType = (domElement: Element, elementType: ANY_ELEMENT_TYPE)
7369
7470// This is the internal implementation: the public entry points set elementType to an appropriate value
7571const createPortalNode = < C extends Component < any > > (
76- defaultElementType : ANY_ELEMENT_TYPE ,
72+ elementType : typeof ELEMENT_TYPE_HTML | typeof ELEMENT_TYPE_SVG ,
7773 options ?: Options
7874) : AnyPortalNode < C > => {
79- const elementType = options ?. containerElement ?? defaultElementType ;
8075 let initialProps = { } as ComponentProps < C > ;
8176
8277 let parent : Node | undefined ;
@@ -85,15 +80,14 @@ const createPortalNode = <C extends Component<any>>(
8580 let element ;
8681
8782 switch ( elementType ) {
88- case ELEMENT_TYPE_HTML_BLOCK :
89- case ELEMENT_TYPE_HTML_INLINE :
90- element = document . createElement ( elementType ) ;
83+ case ELEMENT_TYPE_HTML :
84+ element = document . createElement ( options ?. containerElement ?? 'div' ) ;
9185 break ;
9286 case ELEMENT_TYPE_SVG :
93- element = document . createElementNS ( SVG_NAMESPACE , 'g' ) ;
87+ element = document . createElementNS ( SVG_NAMESPACE , options ?. containerElement ?? 'g' ) ;
9488 break ;
9589 default :
96- throw new Error ( `Invalid element type "${ elementType } " for createPortalNode: must be "div", "span " or "svg".` ) ;
90+ throw new Error ( `Invalid element type "${ elementType } " for createPortalNode: must be "html " or "svg".` ) ;
9791 }
9892
9993 if ( options && typeof options === "object" && options . attributes ) {
@@ -256,14 +250,21 @@ class OutPortal<C extends Component<any>> extends React.PureComponent<OutPortalP
256250 render ( ) {
257251 // Render a placeholder to the DOM, so we can get a reference into
258252 // our location in the DOM, and swap it out for the portaled node.
259- // A <span> placeholder:
260- // - prevents invalid HTML (e.g. inside <p>)
261- // - works fine even for SVG.
262- return < span ref = { this . placeholderNode } /> ;
253+ const tagName = this . props . node . element . tagName ;
254+
255+ // SVG tagName is lowercase and case sensitive, HTML is uppercase and case insensitive.
256+ // React.createElement expects lowercase first letter to treat as non-component element.
257+ // (Passing uppercase type won't break anything, but React warns otherwise:)
258+ // https://github.com/facebook/react/blob/8039f1b2a05d00437cd29707761aeae098c80adc/CHANGELOG.md?plain=1#L1984
259+ const type = this . props . node . elementType === ELEMENT_TYPE_HTML
260+ ? tagName . toLowerCase ( )
261+ : tagName ;
262+
263+ return React . createElement ( type , { ref : this . placeholderNode } ) ;
263264 }
264265}
265266
266- const createHtmlPortalNode = createPortalNode . bind ( null , ELEMENT_TYPE_HTML_BLOCK ) as
267+ const createHtmlPortalNode = createPortalNode . bind ( null , ELEMENT_TYPE_HTML ) as
267268 < C extends Component < any > = Component < any > > ( options ?: HtmlOptions ) => HtmlPortalNode < C > ;
268269const createSvgPortalNode = createPortalNode . bind ( null , ELEMENT_TYPE_SVG ) as
269270 < C extends Component < any > = Component < any > > ( options ?: SvgOptions ) => SvgPortalNode < C > ;
0 commit comments