UNPKG

victory-core

Version:
384 lines (362 loc) 16.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.emulateReactEvent = void 0; exports.getComponentEvents = getComponentEvents; exports.getEventState = getEventState; exports.getEvents = getEvents; exports.getExternalMutation = getExternalMutation; exports.getExternalMutations = getExternalMutations; exports.getExternalMutationsWithChildren = getExternalMutationsWithChildren; exports.getGlobalEventNameFromKey = getGlobalEventNameFromKey; exports.getGlobalEvents = void 0; exports.getPartialEvents = getPartialEvents; exports.getScopedEvents = getScopedEvents; exports.omitGlobalEvents = void 0; var _isEmpty = _interopRequireDefault(require("lodash/isEmpty")); var _pickBy = _interopRequireDefault(require("lodash/pickBy")); var _omitBy = _interopRequireDefault(require("lodash/omitBy")); var _uniq = _interopRequireDefault(require("lodash/uniq")); var _helpers = require("./helpers"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } const GLOBAL_EVENT_REGEX = /^onGlobal(.*)$/; // Normally we'd use Template Literal Types, but we're avoiding it to maximize TS compatibility with TS < 4.1 // `on${Capitalize<string>}`; /* Returns all own and shared events that should be attached to a single target element, * i.e. an individual bar specified by target: "data", eventKey: [index]. * Returned events are scoped to the appropriate state. Either that of the component itself * (i.e. VictoryBar) in the case of own events, or that of the parent component * (i.e. VictoryChart) in the case of shared events */ // eslint-disable-next-line max-params function getEvents(props, target, eventKey, // eslint-disable-next-line no-shadow getScopedEvents) { // Returns all events that apply to a particular target element const getEventsByTarget = events => { const getSelectedEvents = () => { const targetEvents = events.reduce((memo, event) => { if (event.target !== undefined) { const matchesTarget = Array.isArray(event.target) ? event.target.includes(target) : `${event.target}` === `${target}`; return matchesTarget ? memo.concat(event) : memo; } return memo.concat(event); }, []); if (eventKey !== undefined && target !== "parent") { return targetEvents.filter(obj => { const targetKeys = obj.eventKey; const useKey = key => key ? `${key}` === `${eventKey}` : true; return Array.isArray(targetKeys) ? targetKeys.some(k => useKey(k)) : useKey(targetKeys); }); } return targetEvents; }; const selectedEvents = getSelectedEvents(); return Array.isArray(selectedEvents) && selectedEvents.reduce((memo, event) => { return event ? Object.assign(memo, event.eventHandlers) : memo; }, {}); }; /* Returns all events from props and defaultEvents from components. Events handlers * specified in props will override handlers for the same event if they are also * specified in defaultEvents of a sub-component */ const getAllEvents = () => { // Mandatory usage: `getEvents.bind(this)` if (Array.isArray(this.componentEvents)) { return Array.isArray(props.events) ? this.componentEvents.concat(...props.events) : this.componentEvents; } return props.events; }; const allEvents = getAllEvents(); const ownEvents = allEvents && (0, _helpers.isFunction)(getScopedEvents) ? getScopedEvents(getEventsByTarget(allEvents), target) : undefined; if (!props.sharedEvents) { return ownEvents; } const getSharedEvents = props.sharedEvents.getEvents; const sharedEvents = props.sharedEvents.events && getSharedEvents(getEventsByTarget(props.sharedEvents.events), target); return Object.assign({}, sharedEvents, ownEvents); } /* Returns a modified events object where each event handler is replaced by a new * function that calls the original handler and then calls setState with the return * of the original event handler assigned to state property that maps to the target * element. */ // eslint-disable-next-line max-params function getScopedEvents(events, namespace, childType, baseProps) { if ((0, _isEmpty.default)(events)) { return {}; } // Mandatory usage: `getScopedEvents.bind(this)` const newBaseProps = baseProps || this.baseProps; // returns the original base props or base state of a given target element const getTargetProps = (identifier, type) => { const { childName, target, key } = identifier; const baseType = type === "props" ? newBaseProps : this.state || {}; const base = childName === undefined || childName === null || !baseType[childName] ? baseType : baseType[childName]; return key === "parent" ? base.parent : base[key] && base[key][target]; }; // Returns the state object with the mutation caused by a given eventReturn // applied to the appropriate property on the state object const parseEvent = (eventReturn, eventKey) => { const childNames = namespace === "parent" ? eventReturn.childName : eventReturn.childName || childType; const target = eventReturn.target || namespace; // returns all eventKeys to modify for a targeted childName const getKeys = childName => { if (target === "parent") { return "parent"; } if (eventReturn.eventKey === "all") { return newBaseProps[childName] ? Object.keys(newBaseProps[childName]).filter(value => value !== "parent") : Object.keys(newBaseProps).filter(value => value !== "parent"); } else if (eventReturn.eventKey === undefined && eventKey === "parent") { return newBaseProps[childName] ? Object.keys(newBaseProps[childName]) : Object.keys(newBaseProps); } return eventReturn.eventKey !== undefined ? eventReturn.eventKey : eventKey; }; // returns the state object with mutated props applied for a single key const getMutationObject = (key, childName) => { const baseState = this.state || {}; if (!(0, _helpers.isFunction)(eventReturn.mutation)) { return baseState; } const mutationTargetProps = getTargetProps({ childName, key, target }, "props"); const mutationTargetState = getTargetProps({ childName, key, target }, "state"); const mutatedProps = eventReturn.mutation(Object.assign({}, mutationTargetProps, mutationTargetState), newBaseProps); const childState = baseState[childName] || {}; const filterState = state => { if (state[key] && state[key][target]) { delete state[key][target]; } if (state[key] && !Object.keys(state[key]).length) { delete state[key]; } return state; }; const extendState = state => { return target === "parent" ? Object.assign(state, { [key]: Object.assign(state[key] || {}, mutatedProps) }) : Object.assign(state, { [key]: Object.assign(state[key] || {}, { [target]: mutatedProps }) }); }; const updateState = state => { return mutatedProps ? extendState(state) : filterState(state); }; return childName !== undefined && childName !== null ? Object.assign(baseState, { [childName]: updateState(childState) }) : updateState(baseState); }; // returns entire mutated state for a given childName const getReturnByChild = childName => { const mutationKeys = getKeys(childName); return Array.isArray(mutationKeys) ? mutationKeys.reduce((memo, key) => { return Object.assign(memo, getMutationObject(key, childName)); }, {}) : getMutationObject(mutationKeys, childName); }; // returns an entire mutated state for all children const allChildNames = childNames === "all" ? Object.keys(newBaseProps).filter(value => value !== "parent") : childNames; return Array.isArray(allChildNames) ? allChildNames.reduce((memo, childName) => { return Object.assign(memo, getReturnByChild(childName)); }, {}) : getReturnByChild(allChildNames); }; // Parses an array of event returns into a single state mutation const parseEventReturn = (eventReturn, eventKey) => { return Array.isArray(eventReturn) ? eventReturn.reduce((memo, props) => Object.assign({}, memo, parseEvent(props, eventKey)), {}) : parseEvent(eventReturn, eventKey); }; const compileCallbacks = eventReturn => { const getCallback = obj => (0, _helpers.isFunction)(obj.callback) && obj.callback; const callbacks = Array.isArray(eventReturn) ? eventReturn.map(evtObj => getCallback(evtObj)) : [getCallback(eventReturn)]; const callbackArray = callbacks.filter(callback => callback !== false); return callbackArray.length ? () => callbackArray.forEach(callback => callback()) : undefined; }; // A function that calls a particular event handler, parses its return // into a state mutation, and calls setState // eslint-disable-next-line max-params const onEvent = (evt, childProps, eventKey, eventName) => { const eventReturn = events[eventName](evt, childProps, eventKey, this); if (!(0, _isEmpty.default)(eventReturn)) { const callbacks = compileCallbacks(eventReturn); this.setState(parseEventReturn(eventReturn, eventKey), callbacks); } }; // returns a new events object with enhanced event handlers return Object.keys(events).reduce((memo, event) => { memo[event] = onEvent; return memo; }, {}); } /* * Returns a partially applied event handler for a specific target element * This allows event handlers to have access to props controlling each element */ function getPartialEvents(events, eventKey, childProps) { if (!events) return {}; return Object.keys(events).reduce((memo, eventName) => { const appliedEvent = evt => events[eventName](evt, childProps, eventKey, eventName); memo[eventName] = appliedEvent; return memo; }, {}); } /* Returns the property of the state object corresponding to event changes for * a particular element */ // eslint-disable-next-line max-params function getEventState(eventKey, namespace, childType) { // Mandatory usage: `getEventState.bind(this)` const state = this.state || {}; if (!childType) { return eventKey === "parent" ? state[eventKey] && state[eventKey][namespace] || state[eventKey] : state[eventKey] && state[eventKey][namespace]; } return state[childType] && state[childType][eventKey] && state[childType][eventKey][namespace]; } /** * Returns a set of all mutations for shared events * * @param {Array} mutations an array of mutations objects * @param {Object} baseProps an object that describes all props for children of VictorySharedEvents * @param {Object} baseState an object that describes state for children of VictorySharedEvents * @param {Array} childNames an array of childNames * * @return {Object} a object describing all mutations for VictorySharedEvents */ // eslint-disable-next-line max-params function getExternalMutationsWithChildren(mutations, baseProps, baseState, childNames) { if (baseProps === void 0) { baseProps = {}; } if (baseState === void 0) { baseState = {}; } return childNames.reduce((memo, childName) => { const childState = baseState[childName]; const mutation = getExternalMutations(mutations, baseProps[childName], baseState[childName], childName); memo[childName] = mutation ? mutation : childState; return (0, _pickBy.default)(memo, v => !(0, _isEmpty.default)(v)); }, {}); } /** * Returns a set of all mutations for a component * * @param {Array} mutations an array of mutations objects * @param {Object} baseProps a props object (scoped to a childName when used by shared events) * @param {Object} baseState a state object (scoped to a childName when used by shared events) * @param {String} childName an optional childName * * @return {Object} a object describing mutations for a given component */ // eslint-disable-next-line max-params function getExternalMutations(mutations, baseProps, baseState, childName) { if (baseProps === void 0) { baseProps = {}; } if (baseState === void 0) { baseState = {}; } const eventKeys = Object.keys(baseProps); return eventKeys.reduce((memo, eventKey) => { const keyState = baseState[eventKey] || {}; const keyProps = baseProps[eventKey] || {}; if (eventKey === "parent") { const identifier = { eventKey, target: "parent" }; const mutation = getExternalMutation(mutations, keyProps, keyState, identifier); memo[eventKey] = mutation !== undefined ? Object.assign({}, keyState, mutation) : keyState; } else { // use keys from both state and props so that elements not intially included in baseProps // will be used. (i.e. labels) const targets = (0, _uniq.default)(Object.keys(keyProps).concat(Object.keys(keyState))); memo[eventKey] = targets.reduce((m, target) => { const identifier = { eventKey, target, childName }; const mutation = getExternalMutation(mutations, keyProps[target], keyState[target], identifier); m[target] = mutation !== undefined ? Object.assign({}, keyState[target], mutation) : keyState[target]; return (0, _pickBy.default)(m, v => !(0, _isEmpty.default)(v)); }, {}); } return (0, _pickBy.default)(memo, v => !(0, _isEmpty.default)(v)); }, {}); } /** * Returns a set of mutations for a particular element given scoped baseProps and baseState * * @param {Array} mutations an array of mutations objects * @param {Object} baseProps a props object (scoped the element specified by the identifier) * @param {Object} baseState a state object (scoped the element specified by the identifier) * @param {Object} identifier { eventKey, target, childName } * * @return {Object | undefined} a object describing mutations for a given element, or undefined */ // eslint-disable-next-line max-params function getExternalMutation(mutations, baseProps, baseState, identifier) { const filterMutations = (mutation, type) => { if (typeof mutation[type] === "string") { return mutation[type] === "all" || mutation[type] === identifier[type]; } else if (Array.isArray(mutation[type])) { // coerce arrays to strings before matching const stringArray = mutation[type].map(m => `${m}`); return stringArray.includes(identifier[type]); } return false; }; let scopedMutations = Array.isArray(mutations) ? mutations : [mutations]; if (identifier.childName) { scopedMutations = mutations.filter(m => filterMutations(m, "childName")); } // find any mutation objects that match the target const targetMutations = scopedMutations.filter(m => filterMutations(m, "target")); if ((0, _isEmpty.default)(targetMutations)) { return undefined; } const keyMutations = targetMutations.filter(m => filterMutations(m, "eventKey")); if ((0, _isEmpty.default)(keyMutations)) { return undefined; } return keyMutations.reduce((memo, curr) => { const mutationFunction = curr && (0, _helpers.isFunction)(curr.mutation) ? curr.mutation : () => undefined; const currentMutation = mutationFunction(Object.assign({}, baseProps, baseState)); return Object.assign({}, memo, currentMutation); }, {}); } /* Returns an array of defaultEvents from sub-components of a given component. * i.e. any static `defaultEvents` on `labelComponent` will be returned */ function getComponentEvents(props, components) { const events = Array.isArray(components) && components.reduce((memo, componentName) => { const component = props[componentName]; const defaultEvents = component && component.type && component.type.defaultEvents; const componentEvents = (0, _helpers.isFunction)(defaultEvents) ? defaultEvents(component.props) : defaultEvents; return Array.isArray(componentEvents) ? memo.concat(...componentEvents) : memo; }, []); return events && events.length ? events : undefined; } function getGlobalEventNameFromKey(key) { const match = key.match(GLOBAL_EVENT_REGEX); return match && match[1] && match[1].toLowerCase(); } const getGlobalEvents = events => (0, _pickBy.default)(events, (_, key) => GLOBAL_EVENT_REGEX.test(key)); exports.getGlobalEvents = getGlobalEvents; const omitGlobalEvents = events => (0, _omitBy.default)(events, (_, key) => GLOBAL_EVENT_REGEX.test(key)); exports.omitGlobalEvents = omitGlobalEvents; const emulateReactEvent = event => Object.assign(event, { nativeEvent: event }); exports.emulateReactEvent = emulateReactEvent;