victory-core 37.0.2 → 37.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/dist/victory-core.js +395 -358
- package/dist/victory-core.min.js +1 -1
- package/es/index.d.ts +2 -1
- package/es/index.js +2 -1
- package/es/types/prop-types.d.ts +1 -0
- package/es/victory-container/victory-container.d.ts +29 -35
- package/es/victory-container/victory-container.js +134 -162
- package/es/victory-portal/portal-context.d.ts +9 -8
- package/es/victory-portal/portal-context.js +34 -6
- package/es/victory-portal/portal-outlet.d.ts +12 -0
- package/es/victory-portal/portal-outlet.js +14 -0
- package/es/victory-portal/portal.d.ts +1 -12
- package/es/victory-portal/portal.js +6 -32
- package/es/victory-portal/victory-portal.d.ts +5 -22
- package/es/victory-portal/victory-portal.js +37 -52
- package/es/victory-util/index.d.ts +1 -0
- package/es/victory-util/index.js +1 -0
- package/es/victory-util/merge-refs.d.ts +13 -0
- package/es/victory-util/merge-refs.js +23 -0
- package/lib/index.d.ts +2 -1
- package/lib/index.js +16 -4
- package/lib/types/prop-types.d.ts +1 -0
- package/lib/victory-container/victory-container.d.ts +29 -35
- package/lib/victory-container/victory-container.js +136 -163
- package/lib/victory-portal/portal-context.d.ts +9 -8
- package/lib/victory-portal/portal-context.js +37 -7
- package/lib/victory-portal/portal-outlet.d.ts +12 -0
- package/lib/victory-portal/portal-outlet.js +22 -0
- package/lib/victory-portal/portal.d.ts +1 -12
- package/lib/victory-portal/portal.js +6 -33
- package/lib/victory-portal/victory-portal.d.ts +5 -22
- package/lib/victory-portal/victory-portal.js +37 -52
- package/lib/victory-util/index.d.ts +1 -0
- package/lib/victory-util/index.js +12 -0
- package/lib/victory-util/merge-refs.d.ts +13 -0
- package/lib/victory-util/merge-refs.js +31 -0
- package/package.json +2 -2
- package/src/index.ts +2 -1
- package/src/types/prop-types.ts +7 -0
- package/src/victory-container/victory-container.tsx +167 -195
- package/src/victory-portal/portal-context.tsx +63 -0
- package/src/victory-portal/portal-outlet.tsx +26 -0
- package/src/victory-portal/portal.tsx +5 -42
- package/src/victory-portal/victory-portal.tsx +44 -73
- package/src/victory-util/index.ts +1 -0
- package/src/victory-util/merge-refs.ts +26 -0
- package/src/victory-portal/portal-context.ts +0 -14
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import {
|
|
1
|
+
import React, { useRef } from "react";
|
|
2
|
+
import { uniqueId } from "lodash";
|
|
3
3
|
import { Portal } from "../victory-portal/portal";
|
|
4
|
-
import { PortalContext } from "../victory-portal/portal-context";
|
|
5
|
-
import TimerContext from "../victory-util/timer-context";
|
|
6
|
-
import * as Helpers from "../victory-util/helpers";
|
|
7
4
|
import * as UserProps from "../victory-util/user-props";
|
|
8
5
|
import { OriginType } from "../victory-label/victory-label";
|
|
9
6
|
import { D3Scale } from "../types/prop-types";
|
|
10
7
|
import { VictoryThemeDefinition } from "../victory-theme/types";
|
|
8
|
+
import { mergeRefs } from "../victory-util";
|
|
9
|
+
import { PortalOutlet } from "../victory-portal/portal-outlet";
|
|
10
|
+
import { PortalProvider } from "../victory-portal/portal-context";
|
|
11
11
|
|
|
12
12
|
export interface VictoryContainerProps {
|
|
13
13
|
"aria-describedby"?: string;
|
|
@@ -21,9 +21,6 @@ export interface VictoryContainerProps {
|
|
|
21
21
|
height?: number;
|
|
22
22
|
name?: string;
|
|
23
23
|
origin?: OriginType;
|
|
24
|
-
ouiaId?: number | string;
|
|
25
|
-
ouiaSafe?: boolean;
|
|
26
|
-
ouiaType?: string;
|
|
27
24
|
polar?: boolean;
|
|
28
25
|
portalComponent?: React.ReactElement;
|
|
29
26
|
portalZIndex?: number;
|
|
@@ -39,201 +36,176 @@ export interface VictoryContainerProps {
|
|
|
39
36
|
theme?: VictoryThemeDefinition;
|
|
40
37
|
title?: string;
|
|
41
38
|
width?: number;
|
|
39
|
+
// Props defined by the Open UI Automation (OUIA) 1.0-RC spec
|
|
40
|
+
// See https://ouia.readthedocs.io/en/latest/README.html#ouia-component
|
|
41
|
+
ouiaId?: number | string;
|
|
42
|
+
ouiaSafe?: boolean;
|
|
43
|
+
ouiaType?: string;
|
|
42
44
|
}
|
|
43
45
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
//
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
46
|
+
const defaultProps = {
|
|
47
|
+
className: "VictoryContainer",
|
|
48
|
+
portalComponent: <Portal />,
|
|
49
|
+
portalZIndex: 99,
|
|
50
|
+
responsive: true,
|
|
51
|
+
role: "img",
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export function useVictoryContainer<TProps extends VictoryContainerProps>(
|
|
55
|
+
initialProps: TProps,
|
|
56
|
+
) {
|
|
57
|
+
const props = { ...defaultProps, ...initialProps };
|
|
58
|
+
const { title, desc, width, height, responsive } = props;
|
|
59
|
+
|
|
60
|
+
const localContainerRef = useRef<HTMLDivElement>(null);
|
|
61
|
+
|
|
62
|
+
// Generated ID stored in ref because it needs to persist across renders
|
|
63
|
+
const generatedId = useRef(uniqueId("victory-container-"));
|
|
64
|
+
const containerId = props.containerId ?? generatedId.current;
|
|
65
|
+
|
|
66
|
+
const getIdForElement = (elName: string) => `${containerId}-${elName}`;
|
|
67
|
+
|
|
68
|
+
const userProps = UserProps.getSafeUserProps(props);
|
|
69
|
+
|
|
70
|
+
const dimensions = responsive
|
|
71
|
+
? { width: "100%", height: "100%" }
|
|
72
|
+
: { width, height };
|
|
73
|
+
|
|
74
|
+
const viewBox = responsive ? `0 0 ${width} ${height}` : undefined;
|
|
75
|
+
|
|
76
|
+
const preserveAspectRatio = responsive
|
|
77
|
+
? props.preserveAspectRatio
|
|
78
|
+
: undefined;
|
|
79
|
+
|
|
80
|
+
const ariaLabelledBy =
|
|
81
|
+
[title && getIdForElement("title"), props["aria-labelledby"]]
|
|
82
|
+
.filter(Boolean)
|
|
83
|
+
.join(" ") || undefined;
|
|
84
|
+
|
|
85
|
+
const ariaDescribedBy =
|
|
86
|
+
[desc && getIdForElement("desc"), props["aria-describedby"]]
|
|
87
|
+
.filter(Boolean)
|
|
88
|
+
.join(" ") || undefined;
|
|
89
|
+
|
|
90
|
+
const titleId = getIdForElement("title");
|
|
91
|
+
const descId = getIdForElement("desc");
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
...props,
|
|
95
|
+
titleId,
|
|
96
|
+
descId,
|
|
97
|
+
dimensions,
|
|
98
|
+
viewBox,
|
|
99
|
+
preserveAspectRatio,
|
|
100
|
+
ariaLabelledBy,
|
|
101
|
+
ariaDescribedBy,
|
|
102
|
+
userProps,
|
|
103
|
+
localContainerRef,
|
|
78
104
|
};
|
|
79
|
-
|
|
80
|
-
portalRegister = () => this.portalRef.portalRegister();
|
|
81
|
-
portalDeregister = (key) => this.portalRef.portalDeregister(key);
|
|
82
|
-
|
|
83
|
-
saveContainerRef = (container: HTMLElement) => {
|
|
84
|
-
if (Helpers.isFunction(this.props.containerRef)) {
|
|
85
|
-
this.props.containerRef(container);
|
|
86
|
-
}
|
|
87
|
-
this.containerRef = container;
|
|
88
|
-
return container;
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
handleWheel = (e) => e.preventDefault();
|
|
92
|
-
|
|
93
|
-
componentDidMount() {
|
|
94
|
-
if (this.shouldHandleWheel && this.containerRef) {
|
|
95
|
-
this.containerRef.addEventListener("wheel", this.handleWheel);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
componentWillUnmount() {
|
|
100
|
-
if (this.shouldHandleWheel && this.containerRef) {
|
|
101
|
-
this.containerRef.removeEventListener("wheel", this.handleWheel);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
getIdForElement(elementName) {
|
|
106
|
-
return `${this.containerId}-${elementName}`;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// overridden in custom containers
|
|
110
|
-
getChildren(props) {
|
|
111
|
-
return props.children;
|
|
112
|
-
}
|
|
105
|
+
}
|
|
113
106
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
107
|
+
export const VictoryContainer = (initialProps: VictoryContainerProps) => {
|
|
108
|
+
const {
|
|
109
|
+
role,
|
|
110
|
+
title,
|
|
111
|
+
desc,
|
|
112
|
+
children,
|
|
113
|
+
className,
|
|
114
|
+
portalZIndex,
|
|
115
|
+
portalComponent,
|
|
116
|
+
width,
|
|
117
|
+
height,
|
|
118
|
+
style,
|
|
119
|
+
tabIndex,
|
|
120
|
+
responsive,
|
|
121
|
+
events,
|
|
122
|
+
ouiaId,
|
|
123
|
+
ouiaSafe,
|
|
124
|
+
ouiaType,
|
|
125
|
+
dimensions,
|
|
126
|
+
ariaDescribedBy,
|
|
127
|
+
ariaLabelledBy,
|
|
128
|
+
viewBox,
|
|
129
|
+
preserveAspectRatio,
|
|
130
|
+
userProps,
|
|
131
|
+
titleId,
|
|
132
|
+
descId,
|
|
133
|
+
containerRef,
|
|
134
|
+
localContainerRef,
|
|
135
|
+
} = useVictoryContainer(initialProps);
|
|
136
|
+
|
|
137
|
+
React.useEffect(() => {
|
|
138
|
+
if (!events?.onWheel) return;
|
|
139
|
+
|
|
140
|
+
const handleWheel = (e: WheelEvent) => e.preventDefault();
|
|
141
|
+
|
|
142
|
+
const container = localContainerRef?.current;
|
|
143
|
+
container?.addEventListener("wheel", handleWheel);
|
|
144
|
+
|
|
145
|
+
return () => {
|
|
146
|
+
container?.removeEventListener("wheel", handleWheel);
|
|
122
147
|
};
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
portalZIndex,
|
|
134
|
-
responsive,
|
|
135
|
-
} = props;
|
|
136
|
-
const children = this.getChildren(props);
|
|
137
|
-
const dimensions = responsive
|
|
138
|
-
? { width: "100%", height: "100%" }
|
|
139
|
-
: { width, height };
|
|
140
|
-
const divStyle = Object.assign(
|
|
141
|
-
{
|
|
148
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
149
|
+
}, []);
|
|
150
|
+
|
|
151
|
+
return (
|
|
152
|
+
<div
|
|
153
|
+
className={className}
|
|
154
|
+
style={{
|
|
155
|
+
...style,
|
|
156
|
+
width: responsive ? style?.width : dimensions.width,
|
|
157
|
+
height: responsive ? style?.height : dimensions.height,
|
|
142
158
|
pointerEvents: "none",
|
|
143
159
|
touchAction: "none",
|
|
144
160
|
position: "relative",
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
{
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
161
|
+
}}
|
|
162
|
+
data-ouia-component-id={ouiaId}
|
|
163
|
+
data-ouia-component-type={ouiaType}
|
|
164
|
+
data-ouia-safe={ouiaSafe}
|
|
165
|
+
ref={mergeRefs([localContainerRef, containerRef])}
|
|
166
|
+
>
|
|
167
|
+
<PortalProvider>
|
|
168
|
+
<svg
|
|
169
|
+
width={width}
|
|
170
|
+
height={height}
|
|
171
|
+
tabIndex={tabIndex}
|
|
172
|
+
role={role}
|
|
173
|
+
aria-labelledby={ariaLabelledBy}
|
|
174
|
+
aria-describedby={ariaDescribedBy}
|
|
175
|
+
viewBox={viewBox}
|
|
176
|
+
preserveAspectRatio={preserveAspectRatio}
|
|
177
|
+
style={{ ...dimensions, pointerEvents: "all" }}
|
|
178
|
+
{...userProps}
|
|
179
|
+
{...events}
|
|
180
|
+
>
|
|
181
|
+
{title ? <title id={titleId}>{title}</title> : null}
|
|
182
|
+
{desc ? <desc id={descId}>{desc}</desc> : null}
|
|
183
|
+
{children}
|
|
184
|
+
</svg>
|
|
169
185
|
<div
|
|
170
|
-
style={
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
186
|
+
style={{
|
|
187
|
+
...dimensions,
|
|
188
|
+
zIndex: portalZIndex,
|
|
189
|
+
position: "absolute",
|
|
190
|
+
top: 0,
|
|
191
|
+
left: 0,
|
|
192
|
+
}}
|
|
174
193
|
>
|
|
175
|
-
<
|
|
176
|
-
{
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
{
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
...portalProps,
|
|
187
|
-
ref: this.savePortalRef,
|
|
188
|
-
})}
|
|
189
|
-
</div>
|
|
194
|
+
<PortalOutlet
|
|
195
|
+
as={portalComponent}
|
|
196
|
+
width={width}
|
|
197
|
+
height={height}
|
|
198
|
+
viewBox={viewBox}
|
|
199
|
+
preserveAspectRatio={preserveAspectRatio}
|
|
200
|
+
style={{
|
|
201
|
+
...dimensions,
|
|
202
|
+
overflow: "visible",
|
|
203
|
+
}}
|
|
204
|
+
/>
|
|
190
205
|
</div>
|
|
191
|
-
</
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
width,
|
|
198
|
-
height,
|
|
199
|
-
responsive,
|
|
200
|
-
events,
|
|
201
|
-
title,
|
|
202
|
-
desc,
|
|
203
|
-
tabIndex,
|
|
204
|
-
preserveAspectRatio,
|
|
205
|
-
role,
|
|
206
|
-
} = this.props;
|
|
207
|
-
|
|
208
|
-
const style = responsive
|
|
209
|
-
? this.props.style
|
|
210
|
-
: Helpers.omit(this.props.style!, ["height", "width"]);
|
|
211
|
-
|
|
212
|
-
const userProps = UserProps.getSafeUserProps(this.props);
|
|
213
|
-
|
|
214
|
-
const svgProps = Object.assign(
|
|
215
|
-
{
|
|
216
|
-
width,
|
|
217
|
-
height,
|
|
218
|
-
tabIndex,
|
|
219
|
-
role,
|
|
220
|
-
"aria-labelledby":
|
|
221
|
-
[
|
|
222
|
-
title && this.getIdForElement("title"),
|
|
223
|
-
this.props["aria-labelledby"],
|
|
224
|
-
]
|
|
225
|
-
.filter(Boolean)
|
|
226
|
-
.join(" ") || undefined,
|
|
227
|
-
"aria-describedby":
|
|
228
|
-
[desc && this.getIdForElement("desc"), this.props["aria-describedby"]]
|
|
229
|
-
.filter(Boolean)
|
|
230
|
-
.join(" ") || undefined,
|
|
231
|
-
viewBox: responsive ? `0 0 ${width} ${height}` : undefined,
|
|
232
|
-
preserveAspectRatio: responsive ? preserveAspectRatio : undefined,
|
|
233
|
-
...userProps,
|
|
234
|
-
},
|
|
235
|
-
events,
|
|
236
|
-
);
|
|
237
|
-
return this.renderContainer(this.props, svgProps, style);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
206
|
+
</PortalProvider>
|
|
207
|
+
</div>
|
|
208
|
+
);
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
VictoryContainer.role = "container";
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
export interface PortalContextValue {
|
|
4
|
+
addChild: (id: string, node: React.ReactElement) => void;
|
|
5
|
+
removeChild: (id: string) => void;
|
|
6
|
+
children: Map<string, React.ReactElement>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const PortalContext = React.createContext<
|
|
10
|
+
PortalContextValue | undefined
|
|
11
|
+
>(undefined);
|
|
12
|
+
PortalContext.displayName = "PortalContext";
|
|
13
|
+
|
|
14
|
+
export const usePortalContext = () => {
|
|
15
|
+
const context = React.useContext(PortalContext);
|
|
16
|
+
return context;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export interface PortalProviderProps {
|
|
20
|
+
children?: React.ReactNode;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const PortalProvider = ({ children }: PortalProviderProps) => {
|
|
24
|
+
const [portalChildren, setPortalChildren] = React.useState<
|
|
25
|
+
Map<string, React.ReactElement>
|
|
26
|
+
>(new Map());
|
|
27
|
+
const addChild = React.useCallback(
|
|
28
|
+
(id: string, element: React.ReactElement) => {
|
|
29
|
+
setPortalChildren((prevChildren) => {
|
|
30
|
+
const nextChildren = new Map(prevChildren);
|
|
31
|
+
nextChildren.set(id, element);
|
|
32
|
+
return nextChildren;
|
|
33
|
+
});
|
|
34
|
+
},
|
|
35
|
+
[setPortalChildren],
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const removeChild = React.useCallback(
|
|
39
|
+
(id: string) => {
|
|
40
|
+
setPortalChildren((prevChildren) => {
|
|
41
|
+
const nextChildren = new Map(prevChildren);
|
|
42
|
+
nextChildren.delete(id);
|
|
43
|
+
return nextChildren;
|
|
44
|
+
});
|
|
45
|
+
},
|
|
46
|
+
[setPortalChildren],
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const contextValue: PortalContextValue = React.useMemo(
|
|
50
|
+
() => ({
|
|
51
|
+
addChild,
|
|
52
|
+
removeChild,
|
|
53
|
+
children: portalChildren,
|
|
54
|
+
}),
|
|
55
|
+
[addChild, removeChild, portalChildren],
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<PortalContext.Provider value={contextValue}>
|
|
60
|
+
{children}
|
|
61
|
+
</PortalContext.Provider>
|
|
62
|
+
);
|
|
63
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { usePortalContext } from "./portal-context";
|
|
3
|
+
|
|
4
|
+
export interface PortalOutletProps {
|
|
5
|
+
as: React.ReactElement;
|
|
6
|
+
width?: number;
|
|
7
|
+
height?: number;
|
|
8
|
+
viewBox?: string;
|
|
9
|
+
preserveAspectRatio?: string;
|
|
10
|
+
style?: React.CSSProperties;
|
|
11
|
+
children?: (children: React.ReactElement[]) => React.ReactNode | undefined;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const PortalOutlet = ({
|
|
15
|
+
as: portalComponent,
|
|
16
|
+
...props
|
|
17
|
+
}: PortalOutletProps) => {
|
|
18
|
+
const portalContext = usePortalContext();
|
|
19
|
+
|
|
20
|
+
if (!portalContext) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const children = Array.from(portalContext.children.values());
|
|
25
|
+
return React.cloneElement(portalComponent, props, children);
|
|
26
|
+
};
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { PortalContextValue } from "./portal-context";
|
|
3
2
|
|
|
4
3
|
export interface PortalProps {
|
|
5
4
|
className?: string;
|
|
@@ -9,44 +8,8 @@ export interface PortalProps {
|
|
|
9
8
|
width?: number;
|
|
10
9
|
}
|
|
11
10
|
|
|
12
|
-
export
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
private readonly map: Record<string, React.ReactElement>;
|
|
19
|
-
private index: number;
|
|
20
|
-
|
|
21
|
-
constructor(props: PortalProps) {
|
|
22
|
-
super(props);
|
|
23
|
-
this.map = {};
|
|
24
|
-
this.index = 1;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
public portalRegister = (): number => {
|
|
28
|
-
return ++this.index;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
public portalUpdate = (key: number, element: React.ReactElement) => {
|
|
32
|
-
this.map[key] = element;
|
|
33
|
-
this.forceUpdate();
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
public portalDeregister = (key: number) => {
|
|
37
|
-
delete this.map[key];
|
|
38
|
-
this.forceUpdate();
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
public getChildren() {
|
|
42
|
-
return Object.keys(this.map).map((key) => {
|
|
43
|
-
const el = this.map[key];
|
|
44
|
-
return el ? React.cloneElement(el, { key }) : el;
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Overridden in victory-core-native
|
|
49
|
-
render() {
|
|
50
|
-
return <svg {...this.props}>{this.getChildren()}</svg>;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
11
|
+
export const Portal = React.forwardRef<SVGSVGElement, PortalProps>(
|
|
12
|
+
(props, ref) => {
|
|
13
|
+
return <svg ref={ref} {...props} />;
|
|
14
|
+
},
|
|
15
|
+
);
|
|
@@ -2,85 +2,56 @@ import React from "react";
|
|
|
2
2
|
import { defaults } from "lodash";
|
|
3
3
|
import * as Log from "../victory-util/log";
|
|
4
4
|
import * as Helpers from "../victory-util/helpers";
|
|
5
|
-
import {
|
|
5
|
+
import { usePortalContext } from "./portal-context";
|
|
6
6
|
|
|
7
7
|
export interface VictoryPortalProps {
|
|
8
|
-
children
|
|
8
|
+
children: React.ReactElement;
|
|
9
9
|
groupComponent?: React.ReactElement;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export class VictoryPortal extends React.Component<VictoryPortalProps> {
|
|
17
|
-
static displayName = "VictoryPortal";
|
|
18
|
-
|
|
19
|
-
static role = "portal";
|
|
20
|
-
|
|
21
|
-
static defaultProps = {
|
|
22
|
-
groupComponent: <g />,
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
static contextType = PortalContext;
|
|
26
|
-
private checkedContext!: boolean;
|
|
27
|
-
private portalKey!: number;
|
|
28
|
-
public renderInPlace!: boolean;
|
|
29
|
-
public element!: React.ReactElement;
|
|
30
|
-
|
|
31
|
-
componentDidMount() {
|
|
32
|
-
if (!this.checkedContext) {
|
|
33
|
-
if (typeof this.context.portalUpdate !== "function") {
|
|
34
|
-
const msg =
|
|
35
|
-
"`renderInPortal` is not supported outside of `VictoryContainer`. " +
|
|
36
|
-
"Component will be rendered in place";
|
|
37
|
-
Log.warn(msg);
|
|
38
|
-
this.renderInPlace = true;
|
|
39
|
-
}
|
|
40
|
-
this.checkedContext = true;
|
|
41
|
-
}
|
|
42
|
-
this.forceUpdate();
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
componentDidUpdate() {
|
|
46
|
-
if (!this.renderInPlace) {
|
|
47
|
-
this.portalKey = this.portalKey || this.context.portalRegister();
|
|
48
|
-
this.context.portalUpdate(this.portalKey, this.element);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
12
|
+
const defaultProps: Partial<VictoryPortalProps> = {
|
|
13
|
+
groupComponent: <g />,
|
|
14
|
+
};
|
|
51
15
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
16
|
+
export const VictoryPortal = (initialProps: VictoryPortalProps) => {
|
|
17
|
+
const props = { ...defaultProps, ...initialProps };
|
|
18
|
+
const id = React.useId();
|
|
19
|
+
const portalContext = usePortalContext();
|
|
57
20
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
this.element = child;
|
|
64
|
-
return null;
|
|
21
|
+
if (!portalContext) {
|
|
22
|
+
const msg =
|
|
23
|
+
"`renderInPortal` is not supported outside of `VictoryContainer`. " +
|
|
24
|
+
"Component will be rendered in place";
|
|
25
|
+
Log.warn(msg);
|
|
65
26
|
}
|
|
66
27
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
28
|
+
const children = Array.isArray(props.children)
|
|
29
|
+
? props.children[0]
|
|
30
|
+
: props.children;
|
|
31
|
+
const { groupComponent } = props;
|
|
32
|
+
const childProps = (children && children.props) || {};
|
|
33
|
+
const standardProps = childProps.groupComponent
|
|
34
|
+
? { groupComponent, standalone: false }
|
|
35
|
+
: {};
|
|
36
|
+
const newProps = defaults(
|
|
37
|
+
standardProps,
|
|
38
|
+
childProps,
|
|
39
|
+
Helpers.omit(props, ["children", "groupComponent"]),
|
|
40
|
+
{ key: childProps.key ?? id },
|
|
41
|
+
);
|
|
42
|
+
const child = children && React.cloneElement(children, newProps);
|
|
43
|
+
|
|
44
|
+
React.useEffect(() => {
|
|
45
|
+
portalContext?.addChild(id, child);
|
|
46
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
47
|
+
}, [props.children]);
|
|
48
|
+
|
|
49
|
+
React.useEffect(() => {
|
|
50
|
+
return () => portalContext?.removeChild(id);
|
|
51
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
52
|
+
}, []);
|
|
53
|
+
|
|
54
|
+
return portalContext ? null : child;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
VictoryPortal.role = "portal";
|