This commit is contained in:
Nathan Bierema 2020-05-04 18:18:55 -04:00
parent 469cf7cb2c
commit 460b20369a
20 changed files with 379 additions and 86 deletions

3
.prettierrc Normal file
View File

@ -0,0 +1,3 @@
{
"singleQuote": true
}

View File

@ -0,0 +1 @@
lib

View File

@ -12,6 +12,8 @@
"build:js": "babel src --out-dir lib --extensions \".ts,.tsx\" --source-maps inline", "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": "rimraf ./umd && webpack --progress --config webpack.config.umd.js",
"build:umd:min": "webpack --env.minimize --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", "test": "jest",
"prepare": "npm run build", "prepare": "npm run build",
"prepublishOnly": "npm run test && npm run clean && npm run build && npm run build:umd && npm run build:umd:min", "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-env": "^7.3.1",
"@babel/preset-react": "^7.0.0", "@babel/preset-react": "^7.0.0",
"@babel/preset-typescript": "^7.9.0", "@babel/preset-typescript": "^7.9.0",
"@types/react": "^15.0.0 || ^16.0.0",
"@typescript-eslint/eslint-plugin": "^2.31.0", "@typescript-eslint/eslint-plugin": "^2.31.0",
"@typescript-eslint/parser": "^2.31.0", "@typescript-eslint/parser": "^2.31.0",
"babel-loader": "^8.0.5", "babel-loader": "^8.0.5",
"jest": "^24.1.0", "jest": "^24.1.0",
"react": "^16.0.0", "react": "^15.0.0 || ^16.0.0",
"react-dom": "^16.0.0", "react-dom": "^16.0.0",
"react-test-renderer": "^16.0.0", "react-test-renderer": "^16.0.0",
"rimraf": "^2.5.2", "rimraf": "^2.5.2",

View File

@ -1,8 +1,21 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import JSONArrow from './JSONArrow'; 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<Props, State> {
static propTypes = { static propTypes = {
styling: PropTypes.func.isRequired, styling: PropTypes.func.isRequired,
from: PropTypes.number.isRequired, from: PropTypes.number.isRequired,
@ -11,7 +24,7 @@ export default class ItemRange extends React.Component {
nodeType: PropTypes.string.isRequired nodeType: PropTypes.string.isRequired
}; };
constructor(props) { constructor(props: Props) {
super(props); super(props);
this.state = { expanded: false }; this.state = { expanded: false };
@ -42,7 +55,7 @@ export default class ItemRange extends React.Component {
); );
} }
handleClick() { handleClick = () => {
this.setState({ expanded: !this.state.expanded }); this.setState({ expanded: !this.state.expanded });
} };
} }

View File

@ -1,15 +1,21 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import JSONNestedNode from './JSONNestedNode'; import JSONNestedNode from './JSONNestedNode';
import { CircularPropsPassedThroughJSONNode } from './types';
// Returns the "n Items" string for this node, // Returns the "n Items" string for this node,
// generating and caching it if it hasn't been created yet. // 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'}`; return `${data.length} ${data.length !== 1 ? 'items' : 'item'}`;
} }
interface Props extends CircularPropsPassedThroughJSONNode {
data: any;
nodeType: string;
}
// Configures <JSONNestedNode> to render an Array // Configures <JSONNestedNode> to render an Array
const JSONArrayNode = ({ data, ...props }) => ( const JSONArrayNode: React.FunctionComponent<Props> = ({ data, ...props }) => (
<JSONNestedNode <JSONNestedNode
{...props} {...props}
data={data} data={data}

View File

@ -1,7 +1,22 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { StylingFunction } from 'react-base16-styling';
const JSONArrow = ({ styling, arrowStyle, expanded, nodeType, onClick }) => ( interface Props {
styling: StylingFunction;
arrowStyle?: 'single' | 'double';
expanded: boolean;
nodeType: string;
onClick: React.MouseEventHandler<HTMLDivElement>;
}
const JSONArrow: React.FunctionComponent<Props> = ({
styling,
arrowStyle,
expanded,
nodeType,
onClick
}) => (
<div {...styling('arrowContainer', arrowStyle)} onClick={onClick}> <div {...styling('arrowContainer', arrowStyle)} onClick={onClick}>
<div {...styling(['arrow', 'arrowSign'], nodeType, expanded, arrowStyle)}> <div {...styling(['arrow', 'arrowSign'], nodeType, expanded, arrowStyle)}>
{'\u25B6'} {'\u25B6'}

View File

@ -1,9 +1,10 @@
import React from 'react'; import React from 'react';
import JSONNestedNode from './JSONNestedNode'; import JSONNestedNode from './JSONNestedNode';
import { CircularPropsPassedThroughJSONNode } from './types';
// Returns the "n Items" string for this node, // Returns the "n Items" string for this node,
// generating and caching it if it hasn't been created yet. // 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 count = 0;
let hasMore = false; let hasMore = false;
if (Number.isSafeInteger(data.size)) { if (Number.isSafeInteger(data.size)) {
@ -21,8 +22,13 @@ function createItemString(data, limit) {
return `${hasMore ? '>' : ''}${count} ${count !== 1 ? 'entries' : 'entry'}`; return `${hasMore ? '>' : ''}${count} ${count !== 1 ? 'entries' : 'entry'}`;
} }
interface Props extends CircularPropsPassedThroughJSONNode {
data: any;
nodeType: string;
}
// Configures <JSONNestedNode> to render an iterable // Configures <JSONNestedNode> to render an iterable
export default function JSONIterableNode({ ...props }) { const JSONIterableNode: React.FunctionComponent<Props> = ({ ...props }) => {
return ( return (
<JSONNestedNode <JSONNestedNode
{...props} {...props}
@ -31,4 +37,6 @@ export default function JSONIterableNode({ ...props }) {
createItemString={createItemString} createItemString={createItemString}
/> />
); );
} };
export default JSONIterableNode;

View File

@ -4,12 +4,40 @@ import JSONArrow from './JSONArrow';
import getCollectionEntries from './getCollectionEntries'; import getCollectionEntries from './getCollectionEntries';
import JSONNode from './JSONNode'; import JSONNode from './JSONNode';
import ItemRange from './ItemRange'; import ItemRange from './ItemRange';
import {
CircularPropsPassedThroughJSONNestedNode,
CircularPropsPassedThroughRenderChildNodes
} from './types';
/** /**
* Renders nested values (eg. objects, arrays, lists, etc.) * 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 { const {
nodeType, nodeType,
data, data,
@ -19,7 +47,7 @@ function renderChildNodes(props, from, to) {
postprocessValue, postprocessValue,
sortObjectKeys sortObjectKeys
} = props; } = props;
const childNodes = []; const childNodes: React.ReactNode[] = [];
getCollectionEntries( getCollectionEntries(
nodeType, nodeType,
@ -29,7 +57,7 @@ function renderChildNodes(props, from, to) {
from, from,
to to
).forEach(entry => { ).forEach(entry => {
if (entry.to) { if (isRange(entry)) {
childNodes.push( childNodes.push(
<ItemRange <ItemRange
{...props} {...props}
@ -41,9 +69,9 @@ function renderChildNodes(props, from, to) {
); );
} else { } else {
const { key, value } = entry; const { key, value } = entry;
const isCircular = circularCache.indexOf(value) !== -1; const isCircular = circularCache.includes(value);
const node = ( childNodes.push(
<JSONNode <JSONNode
{...props} {...props}
{...{ postprocessValue, collectionLimit }} {...{ postprocessValue, collectionLimit }}
@ -55,17 +83,25 @@ function renderChildNodes(props, from, to) {
hideRoot={false} hideRoot={false}
/> />
); );
if (node !== false) {
childNodes.push(node);
}
} }
}); });
return childNodes; 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 // calculate individual node expansion if necessary
const expanded = const expanded =
props.shouldExpandNode && !props.isCircular 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<Props, State> {
static propTypes = { static propTypes = {
getItemString: PropTypes.func.isRequired, getItemString: PropTypes.func.isRequired,
nodeTypeIndicator: PropTypes.any, nodeTypeIndicator: PropTypes.any,
@ -104,26 +140,26 @@ export default class JSONNestedNode extends React.Component {
expandable: true expandable: true
}; };
constructor(props) { constructor(props: Props) {
super(props); super(props);
this.state = getStateFromProps(props); this.state = getStateFromProps(props);
} }
UNSAFE_componentWillReceiveProps(nextProps) { UNSAFE_componentWillReceiveProps(nextProps: Props) {
const nextState = getStateFromProps(nextProps); const nextState = getStateFromProps(nextProps);
if (getStateFromProps(this.props).expanded !== nextState.expanded) { if (getStateFromProps(this.props).expanded !== nextState.expanded) {
this.setState(nextState); this.setState(nextState);
} }
} }
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps: Props, nextState: State) {
return ( return (
!!Object.keys(nextProps).find( !!Object.keys(nextProps).find(
key => key =>
key !== 'circularCache' && key !== 'circularCache' &&
(key === 'keyPath' (key === 'keyPath'
? nextProps[key].join('/') !== this.props[key].join('/') ? 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 ) || nextState.expanded !== this.state.expanded
); );
} }
@ -159,7 +195,7 @@ export default class JSONNestedNode extends React.Component {
itemType, itemType,
createItemString(data, collectionLimit) createItemString(data, collectionLimit)
); );
const stylingArgs = [keyPath, nodeType, expanded, expandable]; const stylingArgs = [keyPath, nodeType, expanded, expandable] as const;
return hideRoot ? ( return hideRoot ? (
<li {...styling('rootNode', ...stylingArgs)}> <li {...styling('rootNode', ...stylingArgs)}>

View File

@ -5,8 +5,15 @@ import JSONObjectNode from './JSONObjectNode';
import JSONArrayNode from './JSONArrayNode'; import JSONArrayNode from './JSONArrayNode';
import JSONIterableNode from './JSONIterableNode'; import JSONIterableNode from './JSONIterableNode';
import JSONValueNode from './JSONValueNode'; 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<Props> = ({
getItemString, getItemString,
keyPath, keyPath,
labelRenderer, labelRenderer,
@ -97,7 +104,7 @@ const JSONNode = ({
JSONNode.propTypes = { JSONNode.propTypes = {
getItemString: PropTypes.func.isRequired, getItemString: PropTypes.func.isRequired,
keyPath: PropTypes.arrayOf( keyPath: PropTypes.arrayOf(
PropTypes.oneOfType([PropTypes.string, PropTypes.number]) PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired
).isRequired, ).isRequired,
labelRenderer: PropTypes.func.isRequired, labelRenderer: PropTypes.func.isRequired,
styling: PropTypes.func.isRequired, styling: PropTypes.func.isRequired,

View File

@ -1,16 +1,22 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import JSONNestedNode from './JSONNestedNode'; import JSONNestedNode from './JSONNestedNode';
import { CircularPropsPassedThroughJSONNode } from './types';
// Returns the "n Items" string for this node, // Returns the "n Items" string for this node,
// generating and caching it if it hasn't been created yet. // generating and caching it if it hasn't been created yet.
function createItemString(data) { function createItemString(data: any) {
const len = Object.getOwnPropertyNames(data).length; const len = Object.getOwnPropertyNames(data).length;
return `${len} ${len !== 1 ? 'keys' : 'key'}`; return `${len} ${len !== 1 ? 'keys' : 'key'}`;
} }
interface Props extends CircularPropsPassedThroughJSONNode {
data: any;
nodeType: string;
}
// Configures <JSONNestedNode> to render an Object // Configures <JSONNestedNode> to render an Object
const JSONObjectNode = ({ data, ...props }) => ( const JSONObjectNode: React.FunctionComponent<Props> = ({ data, ...props }) => (
<JSONNestedNode <JSONNestedNode
{...props} {...props}
data={data} data={data}
@ -23,7 +29,7 @@ const JSONObjectNode = ({ data, ...props }) => (
JSONObjectNode.propTypes = { JSONObjectNode.propTypes = {
data: PropTypes.object, data: PropTypes.object,
nodeType: PropTypes.string nodeType: PropTypes.string.isRequired
}; };
export default JSONObjectNode; export default JSONObjectNode;

View File

@ -1,18 +1,25 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { JSONValueNodeCircularPropsProvidedByJSONNode } from './types';
/** /**
* Renders simple values (eg. strings, numbers, booleans, etc) * 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<Props> = ({
nodeType, nodeType,
styling, styling,
labelRenderer, labelRenderer,
keyPath, keyPath,
valueRenderer, valueRenderer,
value, value,
valueGetter valueGetter = value => value
}) => ( }) => (
<li {...styling('value', nodeType, keyPath)}> <li {...styling('value', nodeType, keyPath)}>
<label {...styling(['label', 'valueLabel'], nodeType, keyPath)}> <label {...styling(['label', 'valueLabel'], nodeType, keyPath)}>
@ -29,15 +36,11 @@ JSONValueNode.propTypes = {
styling: PropTypes.func.isRequired, styling: PropTypes.func.isRequired,
labelRenderer: PropTypes.func.isRequired, labelRenderer: PropTypes.func.isRequired,
keyPath: PropTypes.arrayOf( keyPath: PropTypes.arrayOf(
PropTypes.oneOfType([PropTypes.string, PropTypes.number]) PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired
).isRequired, ).isRequired,
valueRenderer: PropTypes.func.isRequired, valueRenderer: PropTypes.func.isRequired,
value: PropTypes.any, value: PropTypes.any,
valueGetter: PropTypes.func valueGetter: PropTypes.func
}; };
JSONValueNode.defaultProps = {
valueGetter: value => value
};
export default JSONValueNode; export default JSONValueNode;

View File

@ -1,7 +1,11 @@
import { createStyling } from 'react-base16-styling'; import {
createStyling,
Base16Theme,
StylingConfig
} from 'react-base16-styling';
import solarized from './themes/solarized'; import solarized from './themes/solarized';
const colorMap = theme => ({ const colorMap = (theme: Base16Theme) => ({
BACKGROUND_COLOR: theme.base00, BACKGROUND_COLOR: theme.base00,
TEXT_COLOR: theme.base07, TEXT_COLOR: theme.base07,
STRING_COLOR: theme.base0B, STRING_COLOR: theme.base0B,
@ -18,7 +22,12 @@ const colorMap = theme => ({
ITEM_STRING_EXPANDED_COLOR: theme.base03 ITEM_STRING_EXPANDED_COLOR: theme.base03
}); });
const valueColorMap = colors => ({ type Color = keyof ReturnType<typeof colorMap>;
type Colors = {
[color in Color]: string;
};
const valueColorMap = (colors: Colors) => ({
String: colors.STRING_COLOR, String: colors.STRING_COLOR,
Date: colors.DATE_COLOR, Date: colors.DATE_COLOR,
Number: colors.NUMBER_COLOR, Number: colors.NUMBER_COLOR,
@ -29,7 +38,7 @@ const valueColorMap = colors => ({
Symbol: colors.SYMBOL_COLOR Symbol: colors.SYMBOL_COLOR
}); });
const getDefaultThemeStyling = theme => { const getDefaultThemeStyling = (theme: Base16Theme): StylingConfig => {
const colors = colorMap(theme); const colors = colorMap(theme);
return { return {
@ -73,7 +82,9 @@ const getDefaultThemeStyling = theme => {
valueText: ({ style }, nodeType) => ({ valueText: ({ style }, nodeType) => ({
style: { style: {
...style, ...style,
color: valueColorMap(colors)[nodeType] color: valueColorMap(colors)[
nodeType as keyof ReturnType<typeof valueColorMap>
]
} }
}), }),

View File

@ -1,4 +1,4 @@
function getLength(type, collection) { function getLength(type: string, collection: any) {
if (type === 'Object') { if (type === 'Object') {
return Object.keys(collection).length; return Object.keys(collection).length;
} else if (type === 'Array') { } else if (type === 'Array') {
@ -8,11 +8,17 @@ function getLength(type, collection) {
return Infinity; return Infinity;
} }
function isIterableMap(collection) { function isIterableMap(collection: any) {
return typeof collection.set === 'function'; return typeof collection.set === 'function';
} }
function getEntries(type, collection, sortObjectKeys, from = 0, to = Infinity) { function getEntries(
type: string,
collection: any,
sortObjectKeys?: ((a: any, b: any) => number) | boolean | undefined,
from = 0,
to = Infinity
): { entries: { key: string | number; value: any }[]; hasMore?: boolean } {
let res; let res;
if (type === 'Object') { if (type === 'Object') {
@ -31,7 +37,7 @@ function getEntries(type, collection, sortObjectKeys, from = 0, to = Infinity) {
res = { res = {
entries: collection entries: collection
.slice(from, to + 1) .slice(from, to + 1)
.map((val, idx) => ({ key: idx + from, value: val })) .map((val: any, idx: number) => ({ key: idx + from, value: val }))
}; };
} else { } else {
let idx = 0; let idx = 0;
@ -74,7 +80,7 @@ function getEntries(type, collection, sortObjectKeys, from = 0, to = Infinity) {
return res; return res;
} }
function getRanges(from, to, limit) { function getRanges(from: number, to: number, limit: number) {
const ranges = []; const ranges = [];
while (to - from > limit * limit) { while (to - from > limit * limit) {
limit = limit * limit; limit = limit * limit;
@ -87,10 +93,10 @@ function getRanges(from, to, limit) {
} }
export default function getCollectionEntries( export default function getCollectionEntries(
type, type: string,
collection, collection: any,
sortObjectKeys, sortObjectKeys: ((a: any, b: any) => number) | boolean | undefined,
limit, limit: number,
from = 0, from = 0,
to = Infinity to = Infinity
) { ) {

View File

@ -7,19 +7,47 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import JSONNode from './JSONNode'; import JSONNode from './JSONNode';
import createStylingFromTheme from './createStylingFromTheme'; import createStylingFromTheme from './createStylingFromTheme';
import { invertTheme } from 'react-base16-styling'; import {
Base16Theme,
invertTheme,
StylingConfig,
StylingFunction,
Theme
} from 'react-base16-styling';
import { CircularPropsPassedThroughJSONTree } from './types';
const identity = value => value; interface Props extends CircularPropsPassedThroughJSONTree {
const expandRootNode = (keyName, data, level) => level === 0; theme: Theme;
const defaultItemString = (type, data, itemType, itemString) => ( invertTheme: boolean;
data: any;
}
interface State {
styling: StylingFunction;
}
const identity = (value: any) => value;
const expandRootNode = (
keyPath: (string | number)[],
data: any,
level: number
) => level === 0;
const defaultItemString = (
type: string,
data: any,
itemType: React.ReactNode,
itemString: string
) => (
<span> <span>
{itemType} {itemString} {itemType} {itemString}
</span> </span>
); );
const defaultLabelRenderer = ([label]) => <span>{label}:</span>; const defaultLabelRenderer = ([label]: (string | number)[]) => (
<span>{label}:</span>
);
const noCustomNode = () => false; const noCustomNode = () => false;
function checkLegacyTheming(theme, props) { function checkLegacyTheming(theme: Theme, props: Props) {
const deprecatedStylingMethodsMap = { const deprecatedStylingMethodsMap = {
getArrowStyle: 'arrow', getArrowStyle: 'arrow',
getListStyle: 'nestedNodeChildren', getListStyle: 'nestedNodeChildren',
@ -30,7 +58,7 @@ function checkLegacyTheming(theme, props) {
const deprecatedStylingMethods = Object.keys( const deprecatedStylingMethods = Object.keys(
deprecatedStylingMethodsMap deprecatedStylingMethodsMap
).filter(name => props[name]); ).filter(name => props[name as keyof Props]);
if (deprecatedStylingMethods.length > 0) { if (deprecatedStylingMethods.length > 0) {
if (typeof theme === 'string') { if (typeof theme === 'string') {
@ -47,10 +75,14 @@ function checkLegacyTheming(theme, props) {
`Styling method "${name}" is deprecated, use "theme" property instead` `Styling method "${name}" is deprecated, use "theme" property instead`
); );
theme[deprecatedStylingMethodsMap[name]] = ({ style }, ...args) => ({ (theme as StylingConfig)[
deprecatedStylingMethodsMap[
name as keyof typeof deprecatedStylingMethodsMap
]
] = ({ style }, ...args) => ({
style: { style: {
...style, ...style,
...props[name](...args) ...props[name as keyof Props](...args)
} }
}); });
}); });
@ -59,19 +91,28 @@ function checkLegacyTheming(theme, props) {
return theme; return theme;
} }
function getStateFromProps(props) { function isStylingConfig(
themeOrStyling: Base16Theme | StylingConfig
): themeOrStyling is StylingConfig {
return (themeOrStyling as StylingConfig).extend !== undefined;
}
function getStateFromProps(props: Props) {
let theme = checkLegacyTheming(props.theme, props); let theme = checkLegacyTheming(props.theme, props);
if (props.invertTheme) { if (props.invertTheme) {
if (typeof theme === 'string') { if (typeof theme === 'string') {
theme = `${theme}:inverted`; theme = `${theme}:inverted`;
} else if (theme && theme.extend) { } else if (theme && isStylingConfig(theme) && theme.extend) {
if (typeof theme === 'string') { if (typeof theme.extend === 'string') {
theme = { ...theme, extend: `${theme.extend}:inverted` }; theme = { ...theme, extend: `${theme.extend}:inverted` };
} else { } else {
theme = { ...theme, extend: invertTheme(theme.extend) }; theme = {
...theme,
extend: invertTheme(theme.extend)
} as StylingConfig;
} }
} else if (theme) { } else if (theme) {
theme = invertTheme(theme); theme = invertTheme(theme as Base16Theme);
} }
} }
return { return {
@ -79,7 +120,7 @@ function getStateFromProps(props) {
}; };
} }
export default class JSONTree extends React.Component { export default class JSONTree extends React.Component<Props, State> {
static propTypes = { static propTypes = {
data: PropTypes.oneOfType([PropTypes.array, PropTypes.object]).isRequired, data: PropTypes.oneOfType([PropTypes.array, PropTypes.object]).isRequired,
hideRoot: PropTypes.bool, hideRoot: PropTypes.bool,
@ -105,22 +146,26 @@ export default class JSONTree extends React.Component {
invertTheme: true invertTheme: true
}; };
constructor(props) { constructor(props: Props) {
super(props); super(props);
this.state = getStateFromProps(props); this.state = getStateFromProps(props);
} }
UNSAFE_componentWillReceiveProps(nextProps) { UNSAFE_componentWillReceiveProps(nextProps: Props) {
if (['theme', 'invertTheme'].find(k => nextProps[k] !== this.props[k])) { if (
['theme', 'invertTheme'].find(
k => nextProps[k as keyof Props] !== this.props[k as keyof Props]
)
) {
this.setState(getStateFromProps(nextProps)); this.setState(getStateFromProps(nextProps));
} }
} }
shouldComponentUpdate(nextProps) { shouldComponentUpdate(nextProps: Props) {
return !!Object.keys(nextProps).find(k => return !!Object.keys(nextProps).find(k =>
k === 'keyPath' k === 'keyPath'
? nextProps[k].join('/') !== this.props[k].join('/') ? nextProps[k].join('/') !== this.props[k].join('/')
: nextProps[k] !== this.props[k] : nextProps[k as keyof Props] !== this.props[k as keyof Props]
); );
} }

View File

@ -1,4 +1,4 @@
export default function objType(obj) { export default function objType(obj: any) {
const type = Object.prototype.toString.call(obj).slice(8, -1); const type = Object.prototype.toString.call(obj).slice(8, -1);
if (type === 'Object' && typeof obj[Symbol.iterator] === 'function') { if (type === 'Object' && typeof obj[Symbol.iterator] === 'function') {
return 'Iterable'; return 'Iterable';

View File

@ -0,0 +1,52 @@
declare module 'react-base16-styling' {
import React from 'react';
export interface Styling {
style?: React.CSSProperties;
className?: string;
}
export interface Base16Theme {
scheme?: string;
author?: string;
base00: string;
base01: string;
base02: string;
base03: string;
base04: string;
base05: string;
base06: string;
base07: string;
base08: string;
base09: string;
base0A: string;
base0B: string;
base0C: string;
base0D: string;
base0E: string;
base0F: string;
}
export type StylingValue =
| string
| React.CSSProperties
| ((styling: Styling, ...rest: any[]) => Styling);
export type StylingConfig = { [name: string]: StylingValue } & {
extend?: string | Base16Theme;
};
export type Theme = string | Base16Theme | StylingConfig;
export type StylingFunction = (
keys: string | Array<string | undefined | null>,
...rest: any[]
) => Styling;
export function invertTheme(base16Theme: Base16Theme): Base16Theme;
export function createStyling(
getDefaultStyling: (base16Theme: Base16Theme) => StylingConfig,
options?: { defaultBase16?: Theme; base16Themes?: Theme[] }
): (theme: Theme, invertTheme?: boolean) => StylingFunction;
}

View File

@ -0,0 +1,74 @@
import React from 'react';
import { StylingFunction } from 'react-base16-styling';
interface SharedCircularPropsPassedThroughJSONTree {
keyPath: (string | number)[];
labelRenderer: (
keyPath: (string | number)[],
nodeType: string,
expanded: boolean,
expandable: boolean
) => React.ReactNode;
}
interface SharedCircularPropsProvidedByJSONTree
extends SharedCircularPropsPassedThroughJSONTree {
styling: StylingFunction;
}
interface JSONValueNodeCircularPropsPassedThroughJSONTree {
valueRenderer: (
valueAsString: any,
value: any,
...keyPath: (string | number)[]
) => React.ReactNode;
}
export type JSONValueNodeCircularPropsProvidedByJSONNode = SharedCircularPropsProvidedByJSONTree &
JSONValueNodeCircularPropsPassedThroughJSONTree;
interface JSONNestedNodeCircularPropsPassedThroughJSONTree {
shouldExpandNode: (
keyPath: (string | number)[],
data: any,
level: number
) => boolean;
hideRoot: boolean;
getItemString: (
nodeType: string,
data: any,
itemType: React.ReactNode,
itemString: string
) => React.ReactNode;
postprocessValue: (value: any) => any;
isCustomNode: (value: any) => boolean;
collectionLimit: number;
sortObjectKeys?: (a: any, b: any) => number | boolean;
}
export type CircularPropsPassedThroughJSONTree = SharedCircularPropsPassedThroughJSONTree &
JSONValueNodeCircularPropsPassedThroughJSONTree &
JSONNestedNodeCircularPropsPassedThroughJSONTree;
interface JSONNestedNodeCircularPropsPassedThroughJSONNode
extends JSONNestedNodeCircularPropsPassedThroughJSONTree {
circularCache?: any[];
isCircular?: boolean;
level?: number;
}
export type CircularPropsPassedThroughJSONNode = SharedCircularPropsProvidedByJSONTree &
JSONValueNodeCircularPropsPassedThroughJSONTree &
JSONNestedNodeCircularPropsPassedThroughJSONNode;
export interface JSONNestedNodeCircularPropsPassedThroughJSONNestedNode
extends JSONNestedNodeCircularPropsPassedThroughJSONNode {
circularCache: any[];
level: number;
}
export type CircularPropsPassedThroughJSONNestedNode = SharedCircularPropsProvidedByJSONTree &
JSONValueNodeCircularPropsPassedThroughJSONTree &
JSONNestedNodeCircularPropsPassedThroughJSONNestedNode;
export type CircularPropsPassedThroughRenderChildNodes = SharedCircularPropsProvidedByJSONTree &
JSONValueNodeCircularPropsPassedThroughJSONTree &
JSONNestedNodeCircularPropsPassedThroughJSONNestedNode;
export type CircularPropsPassedThroughItemRange = SharedCircularPropsProvidedByJSONTree &
JSONValueNodeCircularPropsPassedThroughJSONTree &
JSONNestedNodeCircularPropsPassedThroughJSONNestedNode;

View File

@ -1,10 +0,0 @@
export default function(hex) {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result
? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
}
: null;
}

View File

@ -2,6 +2,7 @@
"compilerOptions": { "compilerOptions": {
"target": "esnext", "target": "esnext",
"module": "commonjs", "module": "commonjs",
"jsx": "react",
"declaration": true, "declaration": true,
"outDir": "lib", "outDir": "lib",
"strict": true, "strict": true,

View File

@ -2652,6 +2652,19 @@
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
"@types/prop-types@*":
version "15.7.3"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==
"@types/react@^15.0.0 || ^16.0.0":
version "16.9.34"
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.34.tgz#f7d5e331c468f53affed17a8a4d488cd44ea9349"
integrity sha512-8AJlYMOfPe1KGLKyHpflCg5z46n0b5DbRfqDksxBLBTUpB75ypDBAO9eCUcjNwE6LCUslwTz00yyG/X9gaVtow==
dependencies:
"@types/prop-types" "*"
csstype "^2.2.0"
"@types/source-list-map@*": "@types/source-list-map@*":
version "0.1.2" version "0.1.2"
resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9" resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9"
@ -5625,7 +5638,7 @@ cssstyle@^1.0.0:
dependencies: dependencies:
cssom "0.3.x" cssom "0.3.x"
csstype@^2.5.7: csstype@^2.2.0, csstype@^2.5.7:
version "2.6.10" version "2.6.10"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.10.tgz#e63af50e66d7c266edb6b32909cfd0aabe03928b" resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.10.tgz#e63af50e66d7c266edb6b32909cfd0aabe03928b"
integrity sha512-D34BqZU4cIlMCY93rZHbrq9pjTAQJ3U8S8rfBqjwHxkGPThWFjzZDQpgMJY0QViLxth6ZKYiwFBo14RdN44U/w== integrity sha512-D34BqZU4cIlMCY93rZHbrq9pjTAQJ3U8S8rfBqjwHxkGPThWFjzZDQpgMJY0QViLxth6ZKYiwFBo14RdN44U/w==
@ -13857,7 +13870,7 @@ react-treebeard@^3.1.0:
shallowequal "^1.1.0" shallowequal "^1.1.0"
velocity-react "^1.4.1" velocity-react "^1.4.1"
react@^16.0.0, react@^16.4.0, react@^16.4.2, react@^16.7.0: "react@^15.0.0 || ^16.0.0", react@^16.0.0, react@^16.4.0, react@^16.4.2, react@^16.7.0:
version "16.13.1" version "16.13.1"
resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e" resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e"
integrity sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w== integrity sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==