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.
Files changed (48) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/victory-core.js +395 -358
  3. package/dist/victory-core.min.js +1 -1
  4. package/es/index.d.ts +2 -1
  5. package/es/index.js +2 -1
  6. package/es/types/prop-types.d.ts +1 -0
  7. package/es/victory-container/victory-container.d.ts +29 -35
  8. package/es/victory-container/victory-container.js +134 -162
  9. package/es/victory-portal/portal-context.d.ts +9 -8
  10. package/es/victory-portal/portal-context.js +34 -6
  11. package/es/victory-portal/portal-outlet.d.ts +12 -0
  12. package/es/victory-portal/portal-outlet.js +14 -0
  13. package/es/victory-portal/portal.d.ts +1 -12
  14. package/es/victory-portal/portal.js +6 -32
  15. package/es/victory-portal/victory-portal.d.ts +5 -22
  16. package/es/victory-portal/victory-portal.js +37 -52
  17. package/es/victory-util/index.d.ts +1 -0
  18. package/es/victory-util/index.js +1 -0
  19. package/es/victory-util/merge-refs.d.ts +13 -0
  20. package/es/victory-util/merge-refs.js +23 -0
  21. package/lib/index.d.ts +2 -1
  22. package/lib/index.js +16 -4
  23. package/lib/types/prop-types.d.ts +1 -0
  24. package/lib/victory-container/victory-container.d.ts +29 -35
  25. package/lib/victory-container/victory-container.js +136 -163
  26. package/lib/victory-portal/portal-context.d.ts +9 -8
  27. package/lib/victory-portal/portal-context.js +37 -7
  28. package/lib/victory-portal/portal-outlet.d.ts +12 -0
  29. package/lib/victory-portal/portal-outlet.js +22 -0
  30. package/lib/victory-portal/portal.d.ts +1 -12
  31. package/lib/victory-portal/portal.js +6 -33
  32. package/lib/victory-portal/victory-portal.d.ts +5 -22
  33. package/lib/victory-portal/victory-portal.js +37 -52
  34. package/lib/victory-util/index.d.ts +1 -0
  35. package/lib/victory-util/index.js +12 -0
  36. package/lib/victory-util/merge-refs.d.ts +13 -0
  37. package/lib/victory-util/merge-refs.js +31 -0
  38. package/package.json +2 -2
  39. package/src/index.ts +2 -1
  40. package/src/types/prop-types.ts +7 -0
  41. package/src/victory-container/victory-container.tsx +167 -195
  42. package/src/victory-portal/portal-context.tsx +63 -0
  43. package/src/victory-portal/portal-outlet.tsx +26 -0
  44. package/src/victory-portal/portal.tsx +5 -42
  45. package/src/victory-portal/victory-portal.tsx +44 -73
  46. package/src/victory-util/index.ts +1 -0
  47. package/src/victory-util/merge-refs.ts +26 -0
  48. package/src/victory-portal/portal-context.ts +0 -14
@@ -1,13 +1,13 @@
1
- import React from "react";
2
- import { defaults, uniqueId, isObject } from "lodash";
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
- export class VictoryContainer<
45
- TProps extends VictoryContainerProps,
46
- > extends React.Component<TProps> {
47
- static displayName = "VictoryContainer";
48
- static role = "container";
49
-
50
- static defaultProps = {
51
- className: "VictoryContainer",
52
- portalComponent: <Portal />,
53
- portalZIndex: 99,
54
- responsive: true,
55
- role: "img",
56
- };
57
-
58
- static contextType = TimerContext;
59
- private containerId: VictoryContainerProps["containerId"];
60
- // @ts-expect-error Ref will be initialized on mount
61
- private portalRef: Portal;
62
- // @ts-expect-error Ref will be initialized on mount
63
- private containerRef: HTMLElement;
64
- private shouldHandleWheel: boolean;
65
-
66
- constructor(props: TProps) {
67
- super(props);
68
- this.containerId =
69
- !isObject(props) || props.containerId === undefined
70
- ? uniqueId("victory-container-")
71
- : props.containerId;
72
-
73
- this.shouldHandleWheel = !!(props && props.events && props.events.onWheel);
74
- }
75
- savePortalRef = (portal) => {
76
- this.portalRef = portal;
77
- return portal;
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
- portalUpdate = (key, el) => this.portalRef.portalUpdate(key, el);
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
- // Get props defined by the Open UI Automation (OUIA) 1.0-RC spec
115
- // See https://ouia.readthedocs.io/en/latest/README.html#ouia-component
116
- getOUIAProps(props) {
117
- const { ouiaId, ouiaSafe, ouiaType } = props;
118
- return {
119
- ...(ouiaId && { "data-ouia-component-id": ouiaId }),
120
- ...(ouiaType && { "data-ouia-component-type": ouiaType }),
121
- ...(ouiaSafe !== undefined && { "data-ouia-safe": ouiaSafe }),
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
- renderContainer(props, svgProps, style) {
126
- const {
127
- title,
128
- desc,
129
- portalComponent,
130
- className,
131
- width,
132
- height,
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
- } as const,
146
- dimensions,
147
- );
148
- const portalDivStyle = Object.assign(
149
- { zIndex: portalZIndex, position: "absolute", top: 0, left: 0 } as const,
150
- dimensions,
151
- );
152
- const svgStyle = Object.assign({ pointerEvents: "all" }, dimensions);
153
- const portalSvgStyle = Object.assign({ overflow: "visible" }, dimensions);
154
- const portalProps = {
155
- width,
156
- height,
157
- viewBox: svgProps.viewBox,
158
- preserveAspectRatio: svgProps.preserveAspectRatio,
159
- style: portalSvgStyle,
160
- };
161
- return (
162
- <PortalContext.Provider
163
- value={{
164
- portalUpdate: this.portalUpdate,
165
- portalRegister: this.portalRegister,
166
- portalDeregister: this.portalDeregister,
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={defaults({}, style, divStyle)}
171
- className={className}
172
- ref={this.saveContainerRef}
173
- {...this.getOUIAProps(props)}
186
+ style={{
187
+ ...dimensions,
188
+ zIndex: portalZIndex,
189
+ position: "absolute",
190
+ top: 0,
191
+ left: 0,
192
+ }}
174
193
  >
175
- <svg {...svgProps} style={svgStyle}>
176
- {title ? (
177
- <title id={this.getIdForElement("title")}>{title}</title>
178
- ) : null}
179
- {desc ? (
180
- <desc id={this.getIdForElement("desc")}>{desc}</desc>
181
- ) : null}
182
- {children}
183
- </svg>
184
- <div style={portalDivStyle}>
185
- {React.cloneElement(portalComponent, {
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
- </PortalContext.Provider>
192
- );
193
- }
194
-
195
- render() {
196
- const {
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 class Portal
13
- extends React.Component<PortalProps>
14
- implements PortalContextValue
15
- {
16
- static displayName = "Portal";
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 { PortalContext } from "./portal-context";
5
+ import { usePortalContext } from "./portal-context";
6
6
 
7
7
  export interface VictoryPortalProps {
8
- children?: React.ReactElement;
8
+ children: React.ReactElement;
9
9
  groupComponent?: React.ReactElement;
10
10
  }
11
11
 
12
- export interface VictoryPortal {
13
- context: React.ContextType<typeof PortalContext>;
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
- componentWillUnmount() {
53
- if (this.context && this.context.portalDeregister) {
54
- this.context.portalDeregister(this.portalKey);
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
- // Overridden in victory-core-native
59
- renderPortal(child: React.ReactElement) {
60
- if (this.renderInPlace) {
61
- return child;
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
- render() {
68
- const children = (
69
- Array.isArray(this.props.children)
70
- ? this.props.children[0]
71
- : this.props.children
72
- ) as React.ReactElement;
73
- const { groupComponent } = this.props;
74
- const childProps = (children && children.props) || {};
75
- const standardProps = childProps.groupComponent
76
- ? { groupComponent, standalone: false }
77
- : {};
78
- const newProps = defaults(
79
- standardProps,
80
- childProps,
81
- Helpers.omit(this.props, ["children", "groupComponent"]),
82
- );
83
- const child = children && React.cloneElement(children, newProps);
84
- return this.renderPortal(child);
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";
@@ -1,4 +1,5 @@
1
1
  export * from "./add-events";
2
+ export * from "./merge-refs";
2
3
  export * as Axis from "./axis";
3
4
  export * as Collection from "./collection";
4
5
  export * from "./common-props";