UNPKG

victory-core

Version:
314 lines (302 loc) 14.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.addEvents = addEvents; var _react = _interopRequireDefault(require("react")); var _defaults = _interopRequireDefault(require("lodash/defaults")); var _isEmpty = _interopRequireDefault(require("lodash/isEmpty")); var _pick = _interopRequireDefault(require("lodash/pick")); var _reactFastCompare = _interopRequireDefault(require("react-fast-compare")); var _victoryTransition = require("../victory-transition/victory-transition"); var _collection = require("./collection"); var Events = _interopRequireWildcard(require("./events")); var _helpers = require("./helpers"); function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } // DISCLAIMER: // This file is not currently tested, and it is first on the list of files // to refactor in our current refactoring effort. Please do not make changes // to this file without manual testing and/or refactoring and adding tests. const datumHasXandY = datum => { return !(0, _helpers.isNil)(datum._x) && !(0, _helpers.isNil)(datum._y); }; // used for checking state changes. Expected components can be passed in via options const defaultComponents = [{ name: "parent", index: "parent" }, { name: "data" }, { name: "labels" }]; /** * These methods will be implemented by the Mixin, * and are accessible to the Wrapped Component. * * To make your Wrapped Component type-safe, use "interface merging" like so: * @example * interface MyComponent extends EventsMixinClass<MyProps> {} * class MyComponent extends React.Component<MyProps> { ... } */ /** * These fields are calculated by the Mixin */ /** * These are the common roles that we care about internally. */ /** * A component can have any "role", * but there are certain ones that we actually care about internally */ /** * Static component fields used by Victory for common behavior */ /** * This represents the class itself, including static fields */ function addEvents(WrappedComponent, options) { if (options === void 0) { options = {}; } // eslint-disable-next-line @typescript-eslint/no-empty-object-type // @ts-expect-error "TS2545: A mixin class must have a constructor with a single rest parameter of type 'any[]'." class AddEventsMixin extends WrappedComponent { constructor(props) { super(props); this.cacheValues(this.getCalculatedValues(props)); } state = {}; getEventState = Events.getEventState.bind(this); getScopedEvents = Events.getScopedEvents.bind(this); getEvents = (p, target, eventKey) => { return Events.getEvents.call(this, p, target, eventKey, this.getScopedEvents); }; externalMutations = this.getExternalMutations(this.props); calculatedState = this.getStateChanges(this.props); globalEvents = {}; prevGlobalEventKeys = []; boundGlobalEvents = {}; shouldComponentUpdate(nextProps) { const externalMutations = this.getExternalMutations(nextProps); // @ts-expect-error "Property 'animating' does not exist on type EventMixinCommonProps" const animating = this.props.animating || this.props.animate; const newMutation = !(0, _reactFastCompare.default)(externalMutations, this.externalMutations); if (animating || newMutation) { this.cacheValues(this.getCalculatedValues(nextProps)); this.externalMutations = externalMutations; this.applyExternalMutations(nextProps, externalMutations); return true; } const calculatedState = this.getStateChanges(nextProps); if (!(0, _reactFastCompare.default)(this.calculatedState, calculatedState)) { this.cacheValues(this.getCalculatedValues(nextProps)); return true; } if (!(0, _reactFastCompare.default)(this.props, nextProps)) { this.cacheValues(this.getCalculatedValues(nextProps)); return true; } return false; } componentDidMount() { const globalEventKeys = Object.keys(this.globalEvents); globalEventKeys.forEach(key => this.addGlobalListener(key)); this.prevGlobalEventKeys = globalEventKeys; } componentDidUpdate(prevProps) { const calculatedState = this.getStateChanges(prevProps); this.calculatedState = calculatedState; const globalEventKeys = Object.keys(this.globalEvents); const removedGlobalEventKeys = (0, _collection.difference)(this.prevGlobalEventKeys, globalEventKeys); removedGlobalEventKeys.forEach(key => this.removeGlobalListener(key)); const addedGlobalEventKeys = (0, _collection.difference)(globalEventKeys, this.prevGlobalEventKeys); addedGlobalEventKeys.forEach(key => this.addGlobalListener(key)); this.prevGlobalEventKeys = globalEventKeys; } componentWillUnmount() { this.prevGlobalEventKeys.forEach(key => this.removeGlobalListener(key)); } addGlobalListener(key) { const boundListener = event => { const listener = this.globalEvents[key]; return listener && listener(Events.emulateReactEvent(event)); }; this.boundGlobalEvents[key] = boundListener; window.addEventListener(Events.getGlobalEventNameFromKey(key), boundListener); } removeGlobalListener(key) { window.removeEventListener(Events.getGlobalEventNameFromKey(key), this.boundGlobalEvents[key]); } // compile all state changes from own and parent state. Order doesn't matter, as any state // state change should trigger a re-render getStateChanges(props) { if (!this.hasEvents) { return {}; } const getState = (key, type) => { const result = (0, _defaults.default)({}, this.getEventState(key, type), this.getSharedEventState(key, type)); return (0, _isEmpty.default)(result) ? undefined : result; }; const components = options.components || defaultComponents; const stateChanges = components.map(component => { if (!props.standalone && component.name === "parent") { // don't check for changes on parent props for non-standalone components return undefined; } return component.index !== undefined ? getState(component.index, component.name) : this.dataKeys.map(key => getState(key, component.name)).filter(Boolean); }).filter(Boolean); return stateChanges; } applyExternalMutations(props, externalMutations) { if (!(0, _isEmpty.default)(externalMutations)) { const callbacks = props.externalEventMutations.reduce((memo, mutation) => (0, _helpers.isFunction)(mutation.callback) ? memo.concat(mutation.callback) : memo, []); const compiledCallbacks = callbacks.length ? () => { callbacks.forEach(c => c()); } : undefined; this.setState(externalMutations, compiledCallbacks); } } getCalculatedValues(props) { const { sharedEvents } = props; const components = WrappedComponent.expectedComponents; const componentEvents = Events.getComponentEvents(props, components); const getSharedEventState = sharedEvents && (0, _helpers.isFunction)(sharedEvents.getEventState) ? sharedEvents.getEventState : () => undefined; const baseProps = this.getBaseProps(props, getSharedEventState); const dataKeys = Object.keys(baseProps).filter(key => key !== "parent"); const hasEvents = props.events || props.sharedEvents || componentEvents; const events = this.getAllEvents(props); return { componentEvents, getSharedEventState, baseProps, dataKeys, hasEvents, events }; } getExternalMutations(props) { const { sharedEvents, externalEventMutations } = props; return (0, _isEmpty.default)(externalEventMutations) || sharedEvents ? undefined : Events.getExternalMutations(externalEventMutations, this.baseProps, this.state); } cacheValues(obj) { Object.keys(obj).forEach(key => { this[key] = obj[key]; }); } getBaseProps(props, getSharedEventState) { const getSharedEventStateFunction = getSharedEventState || this.getSharedEventState.bind(this); const sharedParentState = getSharedEventStateFunction("parent", "parent"); const parentState = this.getEventState("parent", "parent"); const baseParentProps = (0, _defaults.default)({}, parentState, sharedParentState); const parentPropsList = baseParentProps.parentControlledProps; const parentProps = parentPropsList ? (0, _pick.default)(baseParentProps, parentPropsList) : {}; const modifiedProps = (0, _defaults.default)({}, parentProps, props); return typeof WrappedComponent.getBaseProps === "function" ? WrappedComponent.getBaseProps(modifiedProps) : {}; } getAllEvents(props) { if (Array.isArray(this.componentEvents)) { return Array.isArray(props.events) ? this.componentEvents.concat(...props.events) : this.componentEvents; } return props.events; } getComponentProps(component, type, index) { const name = this.props.name || WrappedComponent.role; const key = this.dataKeys && this.dataKeys[index] || index; const id = `${name}-${type}-${key}`; const baseProps = this.baseProps[key] && this.baseProps[key][type] || this.baseProps[key]; if (!baseProps && !this.hasEvents) { return undefined; } const currentProps = component && typeof component === "object" && "props" in component ? component.props : undefined; if (this.hasEvents) { const baseEvents = this.getEvents(this.props, type, key); const componentProps = (0, _defaults.default)({ index, key: id }, this.getEventState(key, type), this.getSharedEventState(key, type), currentProps, baseProps, { id }); const events = (0, _defaults.default)({}, Events.getPartialEvents(baseEvents, key, componentProps), componentProps.events); return Object.assign({}, componentProps, { events }); } return (0, _defaults.default)({ index, key: id }, currentProps, baseProps, { id }); } renderContainer(component, children) { const isContainer = component.type && component.type.role === "container"; const parentProps = isContainer ? this.getComponentProps(component, "parent", "parent") : {}; if (parentProps.events) { this.globalEvents = Events.getGlobalEvents(parentProps.events); parentProps.events = Events.omitGlobalEvents(parentProps.events); } return /*#__PURE__*/_react.default.cloneElement(component, parentProps, children); } animateComponent(props, defaultAnimationWhitelist) { const animationWhitelist = typeof props.animate === "object" && props.animate?.animationWhitelist || defaultAnimationWhitelist; const Comp = this.constructor; return /*#__PURE__*/_react.default.createElement(_victoryTransition.VictoryTransition, { animate: props.animate, animationWhitelist: animationWhitelist }, /*#__PURE__*/_react.default.createElement(Comp, props)); } // Used by `VictoryLine` and `VictoryArea` renderContinuousData(props) { const { dataComponent, labelComponent, groupComponent } = props; const dataKeys = this.dataKeys.filter(value => value !== "all"); const labelComponents = dataKeys.reduce((memo, key) => { let newMemo = memo; const labelProps = this.getComponentProps(labelComponent, "labels", key); if (labelProps && labelProps.text !== undefined && labelProps.text !== null) { newMemo = newMemo.concat( /*#__PURE__*/_react.default.cloneElement(labelComponent, labelProps)); } return newMemo; }, []); const dataProps = this.getComponentProps(dataComponent, "data", "all"); const children = [/*#__PURE__*/_react.default.cloneElement(dataComponent, dataProps), ...labelComponents]; return this.renderContainer(groupComponent, children); } renderData(props, shouldRenderDatum) { if (shouldRenderDatum === void 0) { shouldRenderDatum = datumHasXandY; } const { dataComponent, labelComponent, groupComponent } = props; const dataComponents = this.dataKeys.reduce((validDataComponents, _dataKey, index) => { const dataProps = this.getComponentProps(dataComponent, "data", index); if (shouldRenderDatum(dataProps.datum)) { validDataComponents.push( /*#__PURE__*/_react.default.cloneElement(dataComponent, dataProps)); } return validDataComponents; }, []); const labelComponents = this.dataKeys.map((_dataKey, index) => { const labelProps = this.getComponentProps(labelComponent, "labels", index); if (labelProps.text !== undefined && labelProps.text !== null) { return /*#__PURE__*/_react.default.cloneElement(labelComponent, labelProps); } return undefined; }).filter(Boolean); const children = [...dataComponents, ...labelComponents]; return this.renderContainer(groupComponent, children); } } return AddEventsMixin; }