diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..544138be --- /dev/null +++ b/.prettierrc @@ -0,0 +1,3 @@ +{ + "singleQuote": true +} diff --git a/packages/react-json-tree/.eslintignore b/packages/react-json-tree/.eslintignore new file mode 100644 index 00000000..a65b4177 --- /dev/null +++ b/packages/react-json-tree/.eslintignore @@ -0,0 +1 @@ +lib diff --git a/packages/react-json-tree/package.json b/packages/react-json-tree/package.json index bbdd6602..ba6b2d80 100644 --- a/packages/react-json-tree/package.json +++ b/packages/react-json-tree/package.json @@ -12,6 +12,8 @@ "build:js": "babel src --out-dir lib --extensions \".ts,.tsx\" --source-maps inline", "build:umd": "rimraf ./umd && webpack --progress --config webpack.config.umd.js", "build:umd:min": "webpack --env.minimize --progress --config webpack.config.umd.js", + "lint": "eslint . --ext .ts,.tsx", + "lint:fix": "eslint . --ext .ts,.tsx --fix", "test": "jest", "prepare": "npm run build", "prepublishOnly": "npm run test && npm run clean && npm run build && npm run build:umd && npm run build:umd:min", @@ -49,11 +51,12 @@ "@babel/preset-env": "^7.3.1", "@babel/preset-react": "^7.0.0", "@babel/preset-typescript": "^7.9.0", + "@types/react": "^15.0.0 || ^16.0.0", "@typescript-eslint/eslint-plugin": "^2.31.0", "@typescript-eslint/parser": "^2.31.0", "babel-loader": "^8.0.5", "jest": "^24.1.0", - "react": "^16.0.0", + "react": "^15.0.0 || ^16.0.0", "react-dom": "^16.0.0", "react-test-renderer": "^16.0.0", "rimraf": "^2.5.2", diff --git a/packages/react-json-tree/src/ItemRange.tsx b/packages/react-json-tree/src/ItemRange.tsx index f7ed29a8..14d1cf8e 100644 --- a/packages/react-json-tree/src/ItemRange.tsx +++ b/packages/react-json-tree/src/ItemRange.tsx @@ -1,8 +1,21 @@ import React from 'react'; import PropTypes from 'prop-types'; import JSONArrow from './JSONArrow'; +import { CircularPropsPassedThroughItemRange } from './types'; -export default class ItemRange extends React.Component { +interface Props extends CircularPropsPassedThroughItemRange { + data: any; + nodeType: string; + from: number; + to: number; + renderChildNodes: (props: Props, from: number, to: number) => React.ReactNode; +} + +interface State { + expanded: boolean; +} + +export default class ItemRange extends React.Component { static propTypes = { styling: PropTypes.func.isRequired, from: PropTypes.number.isRequired, @@ -11,7 +24,7 @@ export default class ItemRange extends React.Component { nodeType: PropTypes.string.isRequired }; - constructor(props) { + constructor(props: Props) { super(props); this.state = { expanded: false }; @@ -42,7 +55,7 @@ export default class ItemRange extends React.Component { ); } - handleClick() { + handleClick = () => { this.setState({ expanded: !this.state.expanded }); - } + }; } diff --git a/packages/react-json-tree/src/JSONArrayNode.tsx b/packages/react-json-tree/src/JSONArrayNode.tsx index 98a95b84..66a9552f 100644 --- a/packages/react-json-tree/src/JSONArrayNode.tsx +++ b/packages/react-json-tree/src/JSONArrayNode.tsx @@ -1,15 +1,21 @@ import React from 'react'; import PropTypes from 'prop-types'; import JSONNestedNode from './JSONNestedNode'; +import { CircularPropsPassedThroughJSONNode } from './types'; // Returns the "n Items" string for this node, // generating and caching it if it hasn't been created yet. -function createItemString(data) { +function createItemString(data: any) { return `${data.length} ${data.length !== 1 ? 'items' : 'item'}`; } +interface Props extends CircularPropsPassedThroughJSONNode { + data: any; + nodeType: string; +} + // Configures to render an Array -const JSONArrayNode = ({ data, ...props }) => ( +const JSONArrayNode: React.FunctionComponent = ({ data, ...props }) => ( ( +interface Props { + styling: StylingFunction; + arrowStyle?: 'single' | 'double'; + expanded: boolean; + nodeType: string; + onClick: React.MouseEventHandler; +} + +const JSONArrow: React.FunctionComponent = ({ + styling, + arrowStyle, + expanded, + nodeType, + onClick +}) => (
{'\u25B6'} diff --git a/packages/react-json-tree/src/JSONIterableNode.tsx b/packages/react-json-tree/src/JSONIterableNode.tsx index f252481c..dff51ad5 100644 --- a/packages/react-json-tree/src/JSONIterableNode.tsx +++ b/packages/react-json-tree/src/JSONIterableNode.tsx @@ -1,9 +1,10 @@ import React from 'react'; import JSONNestedNode from './JSONNestedNode'; +import { CircularPropsPassedThroughJSONNode } from './types'; // Returns the "n Items" string for this node, // generating and caching it if it hasn't been created yet. -function createItemString(data, limit) { +function createItemString(data: any, limit: number) { let count = 0; let hasMore = false; if (Number.isSafeInteger(data.size)) { @@ -21,8 +22,13 @@ function createItemString(data, limit) { return `${hasMore ? '>' : ''}${count} ${count !== 1 ? 'entries' : 'entry'}`; } +interface Props extends CircularPropsPassedThroughJSONNode { + data: any; + nodeType: string; +} + // Configures to render an iterable -export default function JSONIterableNode({ ...props }) { +const JSONIterableNode: React.FunctionComponent = ({ ...props }) => { return ( ); -} +}; + +export default JSONIterableNode; diff --git a/packages/react-json-tree/src/JSONNestedNode.tsx b/packages/react-json-tree/src/JSONNestedNode.tsx index 2c3afcac..3e62815c 100644 --- a/packages/react-json-tree/src/JSONNestedNode.tsx +++ b/packages/react-json-tree/src/JSONNestedNode.tsx @@ -4,12 +4,40 @@ import JSONArrow from './JSONArrow'; import getCollectionEntries from './getCollectionEntries'; import JSONNode from './JSONNode'; import ItemRange from './ItemRange'; +import { + CircularPropsPassedThroughJSONNestedNode, + CircularPropsPassedThroughRenderChildNodes +} from './types'; /** * Renders nested values (eg. objects, arrays, lists, etc.) */ -function renderChildNodes(props, from, to) { +export interface RenderChildNodesProps + extends CircularPropsPassedThroughRenderChildNodes { + data: any; + nodeType: string; +} + +interface Range { + from: number; + to: number; +} + +interface Entry { + key: string | number; + value: any; +} + +function isRange(rangeOrEntry: Range | Entry): rangeOrEntry is Range { + return (rangeOrEntry as Range).to !== undefined; +} + +function renderChildNodes( + props: RenderChildNodesProps, + from?: number, + to?: number +) { const { nodeType, data, @@ -19,7 +47,7 @@ function renderChildNodes(props, from, to) { postprocessValue, sortObjectKeys } = props; - const childNodes = []; + const childNodes: React.ReactNode[] = []; getCollectionEntries( nodeType, @@ -29,7 +57,7 @@ function renderChildNodes(props, from, to) { from, to ).forEach(entry => { - if (entry.to) { + if (isRange(entry)) { childNodes.push( ); - - if (node !== false) { - childNodes.push(node); - } } }); return childNodes; } -function getStateFromProps(props) { +interface Props extends CircularPropsPassedThroughJSONNestedNode { + data: any; + nodeType: string; + nodeTypeIndicator: string; + createItemString: (data: any, collectionLimit: number) => string; + expandable: boolean; +} + +interface State { + expanded: boolean; +} + +function getStateFromProps(props: Props) { // calculate individual node expansion if necessary const expanded = props.shouldExpandNode && !props.isCircular @@ -76,7 +112,7 @@ function getStateFromProps(props) { }; } -export default class JSONNestedNode extends React.Component { +export default class JSONNestedNode extends React.Component { static propTypes = { getItemString: PropTypes.func.isRequired, nodeTypeIndicator: PropTypes.any, @@ -104,26 +140,26 @@ export default class JSONNestedNode extends React.Component { expandable: true }; - constructor(props) { + constructor(props: Props) { super(props); this.state = getStateFromProps(props); } - UNSAFE_componentWillReceiveProps(nextProps) { + UNSAFE_componentWillReceiveProps(nextProps: Props) { const nextState = getStateFromProps(nextProps); if (getStateFromProps(this.props).expanded !== nextState.expanded) { this.setState(nextState); } } - shouldComponentUpdate(nextProps, nextState) { + shouldComponentUpdate(nextProps: Props, nextState: State) { return ( !!Object.keys(nextProps).find( key => key !== 'circularCache' && (key === 'keyPath' ? nextProps[key].join('/') !== this.props[key].join('/') - : nextProps[key] !== this.props[key]) + : nextProps[key as keyof Props] !== this.props[key as keyof Props]) ) || nextState.expanded !== this.state.expanded ); } @@ -159,7 +195,7 @@ export default class JSONNestedNode extends React.Component { itemType, createItemString(data, collectionLimit) ); - const stylingArgs = [keyPath, nodeType, expanded, expandable]; + const stylingArgs = [keyPath, nodeType, expanded, expandable] as const; return hideRoot ? (
  • diff --git a/packages/react-json-tree/src/JSONNode.tsx b/packages/react-json-tree/src/JSONNode.tsx index bc7a3f1a..771f987f 100644 --- a/packages/react-json-tree/src/JSONNode.tsx +++ b/packages/react-json-tree/src/JSONNode.tsx @@ -5,8 +5,15 @@ import JSONObjectNode from './JSONObjectNode'; import JSONArrayNode from './JSONArrayNode'; import JSONIterableNode from './JSONIterableNode'; import JSONValueNode from './JSONValueNode'; +import { CircularPropsPassedThroughJSONNode } from './types'; -const JSONNode = ({ +interface Props extends CircularPropsPassedThroughJSONNode { + keyPath: (string | number)[]; + value: any; + isCustomNode: (value: any) => boolean; +} + +const JSONNode: React.FunctionComponent = ({ getItemString, keyPath, labelRenderer, @@ -97,7 +104,7 @@ const JSONNode = ({ JSONNode.propTypes = { getItemString: PropTypes.func.isRequired, keyPath: PropTypes.arrayOf( - PropTypes.oneOfType([PropTypes.string, PropTypes.number]) + PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired ).isRequired, labelRenderer: PropTypes.func.isRequired, styling: PropTypes.func.isRequired, diff --git a/packages/react-json-tree/src/JSONObjectNode.tsx b/packages/react-json-tree/src/JSONObjectNode.tsx index a8e55827..2e34853a 100644 --- a/packages/react-json-tree/src/JSONObjectNode.tsx +++ b/packages/react-json-tree/src/JSONObjectNode.tsx @@ -1,16 +1,22 @@ import React from 'react'; import PropTypes from 'prop-types'; import JSONNestedNode from './JSONNestedNode'; +import { CircularPropsPassedThroughJSONNode } from './types'; // Returns the "n Items" string for this node, // generating and caching it if it hasn't been created yet. -function createItemString(data) { +function createItemString(data: any) { const len = Object.getOwnPropertyNames(data).length; return `${len} ${len !== 1 ? 'keys' : 'key'}`; } +interface Props extends CircularPropsPassedThroughJSONNode { + data: any; + nodeType: string; +} + // Configures to render an Object -const JSONObjectNode = ({ data, ...props }) => ( +const JSONObjectNode: React.FunctionComponent = ({ data, ...props }) => ( ( JSONObjectNode.propTypes = { data: PropTypes.object, - nodeType: PropTypes.string + nodeType: PropTypes.string.isRequired }; export default JSONObjectNode; diff --git a/packages/react-json-tree/src/JSONValueNode.tsx b/packages/react-json-tree/src/JSONValueNode.tsx index 1dd7e93b..2d47cb15 100644 --- a/packages/react-json-tree/src/JSONValueNode.tsx +++ b/packages/react-json-tree/src/JSONValueNode.tsx @@ -1,18 +1,25 @@ import React from 'react'; import PropTypes from 'prop-types'; +import { JSONValueNodeCircularPropsProvidedByJSONNode } from './types'; /** * Renders simple values (eg. strings, numbers, booleans, etc) */ -const JSONValueNode = ({ +interface Props extends JSONValueNodeCircularPropsProvidedByJSONNode { + nodeType: string; + value: any; + valueGetter?: (value: any) => any; +} + +const JSONValueNode: React.FunctionComponent = ({ nodeType, styling, labelRenderer, keyPath, valueRenderer, value, - valueGetter + valueGetter = value => value }) => (