UNPKG

lucid-ui

Version:

A UI component library from Xandr.

182 lines 7.13 kB
import React from 'react'; import createReactClass from 'create-react-class'; import PropTypes from 'prop-types'; import _ from 'lodash'; import { isPlainObjectOrEsModule, omitFunctionPropsDeep, } from './state-management'; /** * creates a React component */ export function createClass(spec) { const { _isPrivate = false, getDefaultProps, statics = {}, components = {}, reducers = {}, selectors = {}, initialState = getDefaultProps && omitFunctionPropsDeep(getDefaultProps.apply(spec)), propName = null, propTypes = {}, render = () => null, ...restDefinition } = spec; const propTypeValidators = { ...propTypes, ..._.mapValues(spec.components, (componentValue, componentKey) => PropTypes.any /* Props for ${componentValue.displayName || componentKey} */), }; // Intentionally keep this object type inferred so it can be passed to // `createReactClass` const newDefinition = { getDefaultProps, ...restDefinition, statics: { ...statics, ...components, _isPrivate, reducers, selectors, initialState, propName, }, propTypes: propTypeValidators, render, }; if (!_.isUndefined(newDefinition.statics)) { newDefinition.statics.definition = newDefinition; } const newClass = createReactClass(newDefinition); // This conditional (and breaking change) was introduced to help us move from // legacy React classes to functional components & es6 classes which lack // `getDefaultProps`. if (newClass.getDefaultProps) { newClass.defaultProps = newClass.getDefaultProps(); delete newClass.getDefaultProps; } return newClass; } /** * Return all elements matching the specified types */ export function filterTypes(children, types) { if (types === undefined) return []; return _.filter(React.Children.toArray(children), (element) => React.isValidElement(element) && _.includes(_.castArray(types), element.type)); } /** * Return all elements found in props and children of the specified types */ export function findTypes(props, types) { if (types === undefined) { return []; } // get elements from props (using types.propName) const elementsFromProps = _.reduce(_.castArray(types), (acc, type) => { return _.isNil(type.propName) ? [] : createElements(type, _.flatten(_.values(_.pick(props, type.propName)))); }, []); if (props.children === undefined) { return elementsFromProps; } // return elements from props and elements from children return elementsFromProps.concat(filterTypes(props.children, types)); } // return all elements found in props and children of the specified types // export function findTypes<P2>( // props: { children?: React.ReactNode }, // types?: TypesType<P2> // ): React.ReactNode[] { // if (types === undefined) { // return []; // } // // get elements from props (using types.propName) // const elementsFromProps: React.ReactNode[] = _.reduce( // _.castArray<any>(types), // (acc: React.ReactNode[], type): React.ReactNode[] => { // return _.isNil(type.propName) // ? [] // : createElements( // type, // _.flatten(_.values(_.pick(props, type.propName))) // ); // }, // [] // ); // if (props.children === undefined) { // return elementsFromProps; // } // // return elements from props and elements from children // return elementsFromProps.concat(filterTypes<P2>(props.children, types)); // } /** * Return all elements not matching the specified types */ export function rejectTypes(children, types) { types = [].concat(types); // coerce to Array return _.reject(React.Children.toArray(children), (element) => React.isValidElement(element) && _.includes(types, element.type)); } /** * Return an array of elements (of the given type) for each of the values */ export function createElements(type, values = []) { return _.reduce(values, (elements, typeValue) => { if (React.isValidElement(typeValue) && typeValue.type === type) { return elements.concat(typeValue); } else if (isPlainObjectOrEsModule(typeValue) && !React.isValidElement(typeValue)) { return elements.concat(React.createElement(type, typeValue)); } else if (_.isUndefined(typeValue)) { return elements; } else { return elements.concat(React.createElement(type, null, typeValue)); } }, []); } /** * Return the first element found in props and children of the specificed type(s) */ export function getFirst(props, types, defaultValue) { return _.first(findTypes(props, types)) || defaultValue; } /** * Adds any speicial omitted props to an array * @param {string[]} componentProps - an array of the component's props * @param {boolean} targetIsDOMElement - true by default; specifies if the top-level element of the component is a plain DOM Element or a React Component Element * @return {string[]} the array of component props plus the additional omitted keys * */ export function addSpecialOmittedProps(componentProps = [], targetIsDOMElement = true) { // We only want to exclude the `callbackId` key when we're omitting props // destined for a DOM element. // We always want to add the `initialState` key // to the list of excluded props. const additionalOmittedKeys = targetIsDOMElement ? ['initialState', 'callbackId'] : ['initialState']; return componentProps.concat(additionalOmittedKeys); } /** * Deprecated from lucid-ui May 25, 2022 by Noah Yasskin * Do not use this method because * the import PropTypes from 'prop-types' stopped working as desired * component.propTypes does not compile correctly * and props in the passThroughs object leak through * because they are not being omitted. */ // Omit props defined in propTypes of the given type and any extra keys given // in third argument // // We also have a "magic" prop called `callbackId`. // It can be used to identify a component in a list // without having to create extra closures. // // It is always excluded from a DOM elements // // Note: The Partial<P> type is referring to the props passed into the omitProps, // not the props defined on the component. export function omitProps(props, component, keys = [], targetIsDOMElement = true) { // We only want to exclude the `callbackId` key when we're omitting props // destined for a DOM element. // We always want to exclude the `initialState` key. const additionalOmittedKeys = targetIsDOMElement ? ['initialState', 'callbackId'] : ['initialState']; // this is to support non-createClass components that we've converted to TypeScript if (component === undefined) { return _.omit(props, keys.concat(additionalOmittedKeys)); } return _.omit(props, _.keys(component.propTypes).concat(keys).concat(additionalOmittedKeys)); } //# sourceMappingURL=component-types.js.map