victory-create-container 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-create-container.js +1156 -1700
- package/dist/victory-create-container.min.js +1 -1
- package/es/create-container.d.ts +389 -34
- package/es/create-container.js +59 -69
- package/lib/create-container.d.ts +389 -34
- package/lib/create-container.js +58 -69
- package/package.json +7 -7
- package/src/create-container.tsx +169 -0
- package/src/create-container.ts +0 -173
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import {
|
|
2
|
+
VictoryZoomContainer,
|
|
3
|
+
useVictoryZoomContainer,
|
|
4
|
+
} from "victory-zoom-container";
|
|
5
|
+
import {
|
|
6
|
+
VictorySelectionContainer,
|
|
7
|
+
useVictorySelectionContainer,
|
|
8
|
+
} from "victory-selection-container";
|
|
9
|
+
import React from "react";
|
|
10
|
+
import { VictoryContainer } from "victory-core";
|
|
11
|
+
import { forOwn, groupBy, isEmpty, toPairs } from "lodash";
|
|
12
|
+
import {
|
|
13
|
+
VictoryVoronoiContainer,
|
|
14
|
+
useVictoryVoronoiContainer,
|
|
15
|
+
} from "victory-voronoi-container";
|
|
16
|
+
import {
|
|
17
|
+
VictoryCursorContainer,
|
|
18
|
+
useVictoryCursorContainer,
|
|
19
|
+
} from "victory-cursor-container";
|
|
20
|
+
import {
|
|
21
|
+
VictoryBrushContainer,
|
|
22
|
+
useVictoryBrushContainer,
|
|
23
|
+
} from "victory-brush-container";
|
|
24
|
+
|
|
25
|
+
function ensureArray<T>(thing: T): [] | T | T[] {
|
|
26
|
+
if (!thing) {
|
|
27
|
+
return [];
|
|
28
|
+
} else if (!Array.isArray(thing)) {
|
|
29
|
+
return [thing];
|
|
30
|
+
}
|
|
31
|
+
return thing;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const combineEventHandlers = (eventHandlersArray: any[]) => {
|
|
35
|
+
// takes an array of event handler objects and produces one eventHandlers object
|
|
36
|
+
// creates a custom combinedHandler() for events with multiple conflicting handlers
|
|
37
|
+
return eventHandlersArray.reduce((localHandlers, finalHandlers) => {
|
|
38
|
+
forOwn(localHandlers, (localHandler, eventName) => {
|
|
39
|
+
const existingHandler = finalHandlers[eventName];
|
|
40
|
+
if (existingHandler) {
|
|
41
|
+
// create new handler for event that concats the existing handler's mutations with new ones
|
|
42
|
+
finalHandlers[eventName] = function combinedHandler(...params) {
|
|
43
|
+
// named for debug clarity
|
|
44
|
+
// sometimes handlers return undefined; use empty array instead, for concat()
|
|
45
|
+
const existingMutations = ensureArray(existingHandler(...params));
|
|
46
|
+
const localMutations = ensureArray(localHandler(...params));
|
|
47
|
+
return existingMutations.concat(localMutations);
|
|
48
|
+
};
|
|
49
|
+
} else {
|
|
50
|
+
finalHandlers[eventName] = localHandler;
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
return finalHandlers;
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const combineDefaultEvents = (defaultEvents: any[]) => {
|
|
58
|
+
// takes a defaultEvents array and returns one equal or lesser length,
|
|
59
|
+
// by combining any events that have the same target
|
|
60
|
+
const eventsByTarget = groupBy(defaultEvents, "target");
|
|
61
|
+
const events = toPairs(eventsByTarget).map(([target, eventsArray]) => {
|
|
62
|
+
const newEventsArray = eventsArray.filter(Boolean);
|
|
63
|
+
return isEmpty(newEventsArray)
|
|
64
|
+
? null
|
|
65
|
+
: {
|
|
66
|
+
target,
|
|
67
|
+
eventHandlers: combineEventHandlers(
|
|
68
|
+
eventsArray.map((event) => event.eventHandlers),
|
|
69
|
+
),
|
|
70
|
+
// note: does not currently handle eventKey or childName
|
|
71
|
+
};
|
|
72
|
+
});
|
|
73
|
+
return events.filter(Boolean);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export type ContainerType =
|
|
77
|
+
| "zoom"
|
|
78
|
+
| "selection"
|
|
79
|
+
| "brush"
|
|
80
|
+
| "cursor"
|
|
81
|
+
| "voronoi";
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Container hooks are used to provide the container logic to the container components through props and a modified children object
|
|
85
|
+
* - These hooks contain shared logic for both web and Victory Native containers.
|
|
86
|
+
* - In this utility, we call multiple of these hooks with the props returned by the previous to combine the container logic.
|
|
87
|
+
*/
|
|
88
|
+
const CONTAINER_HOOKS = {
|
|
89
|
+
zoom: useVictoryZoomContainer,
|
|
90
|
+
selection: useVictorySelectionContainer,
|
|
91
|
+
brush: useVictoryBrushContainer,
|
|
92
|
+
cursor: useVictoryCursorContainer,
|
|
93
|
+
voronoi: useVictoryVoronoiContainer,
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Container hooks are wrappers that return a VictoryContainer with the props provided by their respective hooks, and the modified children.
|
|
98
|
+
* - These containers are specific to the web. Victory Native has its own container components.
|
|
99
|
+
* - For this utility, we only need the container components to extract the defaultEvents.
|
|
100
|
+
*/
|
|
101
|
+
const CONTAINER_COMPONENTS_WEB = {
|
|
102
|
+
zoom: VictoryZoomContainer,
|
|
103
|
+
selection: VictorySelectionContainer,
|
|
104
|
+
brush: VictoryBrushContainer,
|
|
105
|
+
cursor: VictoryCursorContainer,
|
|
106
|
+
voronoi: VictoryVoronoiContainer,
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
type ContainerComponents = Record<
|
|
110
|
+
ContainerType,
|
|
111
|
+
React.ComponentType<any> & {
|
|
112
|
+
defaultEvents: (props: any) => any[];
|
|
113
|
+
}
|
|
114
|
+
>;
|
|
115
|
+
|
|
116
|
+
export function makeCreateContainerFunction<
|
|
117
|
+
TContainerComponents extends ContainerComponents,
|
|
118
|
+
>(
|
|
119
|
+
containerComponents: TContainerComponents,
|
|
120
|
+
VictoryContainerBase: typeof VictoryContainer,
|
|
121
|
+
) {
|
|
122
|
+
type ContainerProps<T extends ContainerType> = React.ComponentProps<
|
|
123
|
+
TContainerComponents[T]
|
|
124
|
+
>;
|
|
125
|
+
|
|
126
|
+
return function combineContainers<
|
|
127
|
+
TContainerA extends ContainerType,
|
|
128
|
+
TContainerB extends ContainerType,
|
|
129
|
+
>(containerA: TContainerA, containerB: TContainerB) {
|
|
130
|
+
const ContainerA = containerComponents[containerA];
|
|
131
|
+
const ContainerB = containerComponents[containerB];
|
|
132
|
+
const useContainerA = CONTAINER_HOOKS[containerA];
|
|
133
|
+
const useContainerB = CONTAINER_HOOKS[containerB];
|
|
134
|
+
|
|
135
|
+
const CombinedContainer = (
|
|
136
|
+
props: ContainerProps<TContainerA> & ContainerProps<TContainerB>,
|
|
137
|
+
) => {
|
|
138
|
+
const { children: childrenA, props: propsA } = useContainerA(props);
|
|
139
|
+
const { children: combinedChildren, props: combinedProps } =
|
|
140
|
+
useContainerB({
|
|
141
|
+
...propsA,
|
|
142
|
+
children: childrenA,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<VictoryContainerBase {...combinedProps}>
|
|
147
|
+
{combinedChildren}
|
|
148
|
+
</VictoryContainerBase>
|
|
149
|
+
);
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
CombinedContainer.displayName = `Victory${containerA}${containerB}Container`;
|
|
153
|
+
CombinedContainer.role = "container";
|
|
154
|
+
CombinedContainer.defaultEvents = (
|
|
155
|
+
props: ContainerProps<TContainerA> & ContainerProps<TContainerB>,
|
|
156
|
+
) =>
|
|
157
|
+
combineDefaultEvents([
|
|
158
|
+
...ContainerA.defaultEvents(props),
|
|
159
|
+
...ContainerB.defaultEvents(props),
|
|
160
|
+
]);
|
|
161
|
+
|
|
162
|
+
return CombinedContainer;
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export const createContainer = makeCreateContainerFunction(
|
|
167
|
+
CONTAINER_COMPONENTS_WEB,
|
|
168
|
+
VictoryContainer,
|
|
169
|
+
);
|
package/src/create-container.ts
DELETED
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { toPairs, groupBy, forOwn, flow, isEmpty } from "lodash";
|
|
3
|
-
import { Helpers, VictoryContainer, Log } from "victory-core";
|
|
4
|
-
import { voronoiContainerMixin } from "victory-voronoi-container";
|
|
5
|
-
import { zoomContainerMixin } from "victory-zoom-container";
|
|
6
|
-
import { selectionContainerMixin } from "victory-selection-container";
|
|
7
|
-
import { brushContainerMixin } from "victory-brush-container";
|
|
8
|
-
import { cursorContainerMixin } from "victory-cursor-container";
|
|
9
|
-
|
|
10
|
-
export type ContainerType =
|
|
11
|
-
| "brush"
|
|
12
|
-
| "cursor"
|
|
13
|
-
| "selection"
|
|
14
|
-
| "voronoi"
|
|
15
|
-
| "zoom";
|
|
16
|
-
|
|
17
|
-
type MixinFunction = (...args: any[]) => any;
|
|
18
|
-
|
|
19
|
-
function ensureArray<T>(thing: T): [] | T | T[] {
|
|
20
|
-
if (!thing) {
|
|
21
|
-
return [];
|
|
22
|
-
} else if (!Array.isArray(thing)) {
|
|
23
|
-
return [thing];
|
|
24
|
-
}
|
|
25
|
-
return thing;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const combineEventHandlers = (eventHandlersArray: any[]) => {
|
|
29
|
-
// takes an array of event handler objects and produces one eventHandlers object
|
|
30
|
-
// creates a custom combinedHandler() for events with multiple conflicting handlers
|
|
31
|
-
return eventHandlersArray.reduce((localHandlers, finalHandlers) => {
|
|
32
|
-
forOwn(localHandlers, (localHandler, eventName) => {
|
|
33
|
-
const existingHandler = finalHandlers[eventName];
|
|
34
|
-
if (existingHandler) {
|
|
35
|
-
// create new handler for event that concats the existing handler's mutations with new ones
|
|
36
|
-
finalHandlers[eventName] = function combinedHandler(...params) {
|
|
37
|
-
// named for debug clarity
|
|
38
|
-
// sometimes handlers return undefined; use empty array instead, for concat()
|
|
39
|
-
const existingMutations = ensureArray(existingHandler(...params));
|
|
40
|
-
const localMutations = ensureArray(localHandler(...params));
|
|
41
|
-
return existingMutations.concat(localMutations);
|
|
42
|
-
};
|
|
43
|
-
} else {
|
|
44
|
-
finalHandlers[eventName] = localHandler;
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
return finalHandlers;
|
|
48
|
-
});
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
const combineDefaultEvents = (defaultEvents: any[]) => {
|
|
52
|
-
// takes a defaultEvents array and returns one equal or lesser length,
|
|
53
|
-
// by combining any events that have the same target
|
|
54
|
-
const eventsByTarget = groupBy(defaultEvents, "target");
|
|
55
|
-
const events = toPairs(eventsByTarget).map(([target, eventsArray]) => {
|
|
56
|
-
const newEventsArray = eventsArray.filter(Boolean);
|
|
57
|
-
return isEmpty(newEventsArray)
|
|
58
|
-
? null
|
|
59
|
-
: {
|
|
60
|
-
target,
|
|
61
|
-
eventHandlers: combineEventHandlers(
|
|
62
|
-
eventsArray.map((event) => event.eventHandlers),
|
|
63
|
-
),
|
|
64
|
-
// note: does not currently handle eventKey or childName
|
|
65
|
-
};
|
|
66
|
-
});
|
|
67
|
-
return events.filter(Boolean);
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
export const combineContainerMixins = (
|
|
71
|
-
mixins: MixinFunction[],
|
|
72
|
-
Container: React.ComponentType<any>,
|
|
73
|
-
) => {
|
|
74
|
-
// similar to Object.assign(A, B), this function will decide conflicts in favor mixinB.
|
|
75
|
-
// this applies to propTypes and defaultProps.
|
|
76
|
-
// getChildren will call A's getChildren() and pass the resulting children to B's.
|
|
77
|
-
// defaultEvents attempts to resolve any conflicts between A and B's defaultEvents.
|
|
78
|
-
const Classes = mixins.map((mixin) => mixin(Container));
|
|
79
|
-
const instances = Classes.map((Class) => new Class());
|
|
80
|
-
const NaiveCombinedContainer = flow(mixins)(Container);
|
|
81
|
-
|
|
82
|
-
const displayType = Classes.map((Class) => {
|
|
83
|
-
const match = Class.displayName.match(/Victory(.*)Container/);
|
|
84
|
-
return match[1] || "";
|
|
85
|
-
}).join("");
|
|
86
|
-
|
|
87
|
-
return class VictoryCombinedContainer extends NaiveCombinedContainer {
|
|
88
|
-
static displayName = `Victory${displayType}Container`;
|
|
89
|
-
|
|
90
|
-
static propTypes = Classes.reduce(
|
|
91
|
-
(propTypes, Class) => ({ ...propTypes, ...Class.propTypes }),
|
|
92
|
-
{},
|
|
93
|
-
);
|
|
94
|
-
|
|
95
|
-
static defaultProps = Classes.reduce(
|
|
96
|
-
(defaultProps, Class) => ({ ...defaultProps, ...Class.defaultProps }),
|
|
97
|
-
{},
|
|
98
|
-
);
|
|
99
|
-
|
|
100
|
-
static defaultEvents(props) {
|
|
101
|
-
return combineDefaultEvents(
|
|
102
|
-
Classes.reduce((defaultEvents, Class) => {
|
|
103
|
-
const events = Helpers.isFunction(Class.defaultEvents)
|
|
104
|
-
? Class.defaultEvents(props)
|
|
105
|
-
: Class.defaultEvents;
|
|
106
|
-
return [...defaultEvents, ...events];
|
|
107
|
-
}, []),
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
getChildren(props) {
|
|
112
|
-
return instances.reduce(
|
|
113
|
-
(children, instance) => instance.getChildren({ ...props, children }),
|
|
114
|
-
props.children,
|
|
115
|
-
);
|
|
116
|
-
}
|
|
117
|
-
};
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
const checkBehaviorName = (
|
|
121
|
-
behavior: ContainerType,
|
|
122
|
-
behaviors: ContainerType[],
|
|
123
|
-
) => {
|
|
124
|
-
if (behavior && !behaviors.includes(behavior)) {
|
|
125
|
-
Log.warn(
|
|
126
|
-
`"${behavior}" is not a valid behavior. Choose from [${behaviors.join(
|
|
127
|
-
", ",
|
|
128
|
-
)}].`,
|
|
129
|
-
);
|
|
130
|
-
}
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
export const makeCreateContainerFunction =
|
|
134
|
-
(
|
|
135
|
-
mixinMap: Record<ContainerType, MixinFunction[]>,
|
|
136
|
-
Container: React.ComponentType<any>,
|
|
137
|
-
) =>
|
|
138
|
-
(
|
|
139
|
-
behaviorA: ContainerType,
|
|
140
|
-
behaviorB: ContainerType,
|
|
141
|
-
...invalid: ContainerType[]
|
|
142
|
-
) => {
|
|
143
|
-
const behaviors = Object.keys(mixinMap) as ContainerType[];
|
|
144
|
-
|
|
145
|
-
checkBehaviorName(behaviorA, behaviors);
|
|
146
|
-
checkBehaviorName(behaviorB, behaviors);
|
|
147
|
-
|
|
148
|
-
if (invalid.length) {
|
|
149
|
-
Log.warn(
|
|
150
|
-
"too many arguments given to createContainer (maximum accepted: 2).",
|
|
151
|
-
);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const firstMixins = mixinMap[behaviorA];
|
|
155
|
-
const secondMixins = mixinMap[behaviorB] || [];
|
|
156
|
-
|
|
157
|
-
if (!firstMixins) {
|
|
158
|
-
return Container;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
return combineContainerMixins([...firstMixins, ...secondMixins], Container);
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
export const createContainer = makeCreateContainerFunction(
|
|
165
|
-
{
|
|
166
|
-
zoom: [zoomContainerMixin],
|
|
167
|
-
voronoi: [voronoiContainerMixin],
|
|
168
|
-
selection: [selectionContainerMixin],
|
|
169
|
-
cursor: [cursorContainerMixin],
|
|
170
|
-
brush: [brushContainerMixin],
|
|
171
|
-
},
|
|
172
|
-
VictoryContainer,
|
|
173
|
-
);
|