victory-create-container
Version:
Container Helper for Victory
110 lines (108 loc) • 4.91 kB
JavaScript
import React from "react";
import forOwn from "lodash/forOwn";
import groupBy from "lodash/groupBy";
import isEmpty from "lodash/isEmpty";
import toPairs from "lodash/toPairs";
import { VictoryZoomContainer, useVictoryZoomContainer } from "victory-zoom-container";
import { VictorySelectionContainer, useVictorySelectionContainer } from "victory-selection-container";
import { VictoryContainer } from "victory-core";
import { VictoryVoronoiContainer, useVictoryVoronoiContainer } from "victory-voronoi-container";
import { VictoryCursorContainer, useVictoryCursorContainer } from "victory-cursor-container";
import { VictoryBrushContainer, useVictoryBrushContainer } from "victory-brush-container";
function ensureArray(thing) {
if (!thing) {
return [];
} else if (!Array.isArray(thing)) {
return [thing];
}
return thing;
}
const combineEventHandlers = eventHandlersArray => {
// takes an array of event handler objects and produces one eventHandlers object
// creates a custom combinedHandler() for events with multiple conflicting handlers
return eventHandlersArray.reduce((localHandlers, finalHandlers) => {
forOwn(localHandlers, (localHandler, eventName) => {
const existingHandler = finalHandlers[eventName];
if (existingHandler) {
// create new handler for event that concats the existing handler's mutations with new ones
finalHandlers[eventName] = function combinedHandler() {
// named for debug clarity
// sometimes handlers return undefined; use empty array instead, for concat()
const existingMutations = ensureArray(existingHandler(...arguments));
const localMutations = ensureArray(localHandler(...arguments));
return existingMutations.concat(localMutations);
};
} else {
finalHandlers[eventName] = localHandler;
}
});
return finalHandlers;
});
};
const combineDefaultEvents = defaultEvents => {
// takes a defaultEvents array and returns one equal or lesser length,
// by combining any events that have the same target
const eventsByTarget = groupBy(defaultEvents, "target");
const events = toPairs(eventsByTarget).map(_ref => {
let [target, eventsArray] = _ref;
const newEventsArray = eventsArray.filter(Boolean);
return isEmpty(newEventsArray) ? null : {
target,
eventHandlers: combineEventHandlers(eventsArray.map(event => event.eventHandlers))
// note: does not currently handle eventKey or childName
};
});
return events.filter(Boolean);
};
/**
* Container hooks are used to provide the container logic to the container components through props and a modified children object
* - These hooks contain shared logic for both web and Victory Native containers.
* - In this utility, we call multiple of these hooks with the props returned by the previous to combine the container logic.
*/
const CONTAINER_HOOKS = {
zoom: useVictoryZoomContainer,
selection: useVictorySelectionContainer,
brush: useVictoryBrushContainer,
cursor: useVictoryCursorContainer,
voronoi: useVictoryVoronoiContainer
};
/**
* Container hooks are wrappers that return a VictoryContainer with the props provided by their respective hooks, and the modified children.
* - These containers are specific to the web. Victory Native has its own container components.
* - For this utility, we only need the container components to extract the defaultEvents.
*/
const CONTAINER_COMPONENTS_WEB = {
zoom: VictoryZoomContainer,
selection: VictorySelectionContainer,
brush: VictoryBrushContainer,
cursor: VictoryCursorContainer,
voronoi: VictoryVoronoiContainer
};
export function makeCreateContainerFunction(containerComponents, VictoryContainerBase) {
// Helper type to support backwards compatibility with old types
return function combineContainers(containerA, containerB) {
const ContainerA = containerComponents[containerA];
const ContainerB = containerComponents[containerB];
const useContainerA = CONTAINER_HOOKS[containerA];
const useContainerB = CONTAINER_HOOKS[containerB];
const CombinedContainer = props => {
const {
children: childrenA,
props: propsA
} = useContainerA(props);
const {
children: combinedChildren,
props: combinedProps
} = useContainerB({
...propsA,
children: childrenA
});
return /*#__PURE__*/React.createElement(VictoryContainerBase, combinedProps, combinedChildren);
};
CombinedContainer.displayName = `Victory${containerA}${containerB}Container`;
CombinedContainer.role = "container";
CombinedContainer.defaultEvents = props => combineDefaultEvents([...ContainerA.defaultEvents(props), ...ContainerB.defaultEvents(props)]);
return CombinedContainer;
};
}
export const createContainer = makeCreateContainerFunction(CONTAINER_COMPONENTS_WEB, VictoryContainer);