@@ -2,22 +2,19 @@ import * as React from 'react';
2
2
import * as ReactDOM from 'react-dom' ;
3
3
4
4
// 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' ;
7
6
const ELEMENT_TYPE_SVG = 'svg' ;
8
7
9
- type ANY_ELEMENT_TYPE = typeof ELEMENT_TYPE_HTML_BLOCK | typeof ELEMENT_TYPE_HTML_INLINE | typeof ELEMENT_TYPE_SVG ;
10
-
11
8
type BaseOptions = {
12
9
attributes ?: { [ key : string ] : string } ;
13
10
} ;
14
11
15
12
type HtmlOptions = BaseOptions & {
16
- containerElement ?: typeof ELEMENT_TYPE_HTML_BLOCK | typeof ELEMENT_TYPE_HTML_INLINE ;
13
+ containerElement ?: keyof HTMLElementTagNameMap ;
17
14
} ;
18
15
19
16
type SvgOptions = BaseOptions & {
20
- containerElement ?: typeof ELEMENT_TYPE_SVG ;
17
+ containerElement ?: keyof SVGElementTagNameMap ;
21
18
} ;
22
19
23
20
type Options = HtmlOptions | SvgOptions ;
@@ -45,7 +42,7 @@ interface PortalNodeBase<C extends Component<any>> {
45
42
}
46
43
export interface HtmlPortalNode < C extends Component < any > = Component < any > > extends PortalNodeBase < C > {
47
44
element : HTMLElement ;
48
- elementType : typeof ELEMENT_TYPE_HTML_BLOCK | typeof ELEMENT_TYPE_HTML_INLINE ;
45
+ elementType : typeof ELEMENT_TYPE_HTML ;
49
46
}
50
47
export interface SvgPortalNode < C extends Component < any > = Component < any > > extends PortalNodeBase < C > {
51
48
element : SVGElement ;
@@ -54,15 +51,14 @@ export interface SvgPortalNode<C extends Component<any> = Component<any>> extend
54
51
type AnyPortalNode < C extends Component < any > = Component < any > > = HtmlPortalNode < C > | SvgPortalNode < C > ;
55
52
56
53
57
- const validateElementType = ( domElement : Element , elementType : ANY_ELEMENT_TYPE ) => {
54
+ const validateElementType = ( domElement : Element , elementType : typeof ELEMENT_TYPE_HTML | typeof ELEMENT_TYPE_SVG ) => {
58
55
const ownerDocument = ( domElement . ownerDocument ?? document ) as any ;
59
56
// Cast document to `any` because Typescript doesn't know about the legacy `Document.parentWindow` field, and also
60
57
// doesn't believe `Window.HTMLElement`/`Window.SVGElement` can be used in instanceof tests.
61
58
const ownerWindow = ownerDocument . defaultView ?? ownerDocument . parentWindow ?? window ; // `parentWindow` for IE8 and earlier
62
59
63
60
switch ( elementType ) {
64
- case ELEMENT_TYPE_HTML_BLOCK :
65
- case ELEMENT_TYPE_HTML_INLINE :
61
+ case ELEMENT_TYPE_HTML :
66
62
return domElement instanceof ownerWindow . HTMLElement ;
67
63
case ELEMENT_TYPE_SVG :
68
64
return domElement instanceof ownerWindow . SVGElement ;
@@ -73,10 +69,9 @@ const validateElementType = (domElement: Element, elementType: ANY_ELEMENT_TYPE)
73
69
74
70
// This is the internal implementation: the public entry points set elementType to an appropriate value
75
71
const createPortalNode = < C extends Component < any > > (
76
- defaultElementType : ANY_ELEMENT_TYPE ,
72
+ elementType : typeof ELEMENT_TYPE_HTML | typeof ELEMENT_TYPE_SVG ,
77
73
options ?: Options
78
74
) : AnyPortalNode < C > => {
79
- const elementType = options ?. containerElement ?? defaultElementType ;
80
75
let initialProps = { } as ComponentProps < C > ;
81
76
82
77
let parent : Node | undefined ;
@@ -85,15 +80,14 @@ const createPortalNode = <C extends Component<any>>(
85
80
let element ;
86
81
87
82
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' ) ;
91
85
break ;
92
86
case ELEMENT_TYPE_SVG :
93
- element = document . createElementNS ( SVG_NAMESPACE , 'g' ) ;
87
+ element = document . createElementNS ( SVG_NAMESPACE , options ?. containerElement ?? 'g' ) ;
94
88
break ;
95
89
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".` ) ;
97
91
}
98
92
99
93
if ( options && typeof options === "object" && options . attributes ) {
@@ -256,14 +250,21 @@ class OutPortal<C extends Component<any>> extends React.PureComponent<OutPortalP
256
250
render ( ) {
257
251
// Render a placeholder to the DOM, so we can get a reference into
258
252
// 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 } ) ;
263
264
}
264
265
}
265
266
266
- const createHtmlPortalNode = createPortalNode . bind ( null , ELEMENT_TYPE_HTML_BLOCK ) as
267
+ const createHtmlPortalNode = createPortalNode . bind ( null , ELEMENT_TYPE_HTML ) as
267
268
< C extends Component < any > = Component < any > > ( options ?: HtmlOptions ) => HtmlPortalNode < C > ;
268
269
const createSvgPortalNode = createPortalNode . bind ( null , ELEMENT_TYPE_SVG ) as
269
270
< C extends Component < any > = Component < any > > ( options ?: SvgOptions ) => SvgPortalNode < C > ;
0 commit comments