diff --git a/.husky/.gitignore b/.husky/.gitignore
new file mode 100644
index 0000000..31354ec
--- /dev/null
+++ b/.husky/.gitignore
@@ -0,0 +1 @@
+_
diff --git a/package-lock.json b/package-lock.json
index 2c3df91..66c1208 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "react-json-editor-viewer",
- "version": "1.0.7",
+ "version": "1.1.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "react-json-editor-viewer",
- "version": "1.0.7",
+ "version": "1.1.1",
"license": "MIT",
"devDependencies": {
"autoprefixer": "7.1.6",
@@ -43,7 +43,7 @@
"raf": "3.4.0",
"react": "17.0.2",
"react-dev-utils": "^4.2.1",
- "react-dom": "^16.2.0",
+ "react-dom": "17.0.2",
"style-loader": "0.19.0",
"sw-precache-webpack-plugin": "0.11.4",
"url-loader": "0.6.2",
@@ -2256,23 +2256,31 @@
"dev": true
},
"node_modules/chokidar": {
- "version": "3.5.1",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz",
- "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==",
- "dev": true,
+ "version": "3.5.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
+ "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ ],
"optional": true,
"dependencies": {
- "anymatch": "~3.1.1",
+ "anymatch": "~3.1.2",
"braces": "~3.0.2",
- "fsevents": "~2.3.1",
- "glob-parent": "~5.1.0",
+ "glob-parent": "~5.1.2",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
"normalize-path": "~3.0.0",
- "readdirp": "~3.5.0"
+ "readdirp": "~3.6.0"
},
"engines": {
"node": ">= 8.10.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
}
},
"node_modules/chokidar/node_modules/anymatch": {
@@ -11828,15 +11836,17 @@
}
},
"node_modules/react-dom": {
- "version": "16.14.0",
- "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz",
- "integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==",
+ "version": "17.0.2",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
+ "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==",
"dev": true,
"dependencies": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
- "prop-types": "^15.6.2",
- "scheduler": "^0.19.1"
+ "scheduler": "^0.20.2"
+ },
+ "peerDependencies": {
+ "react": "17.0.2"
}
},
"node_modules/react-error-overlay": {
@@ -11919,9 +11929,9 @@
}
},
"node_modules/readdirp": {
- "version": "3.5.0",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz",
- "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==",
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"dev": true,
"optional": true,
"dependencies": {
@@ -12483,9 +12493,9 @@
"dev": true
},
"node_modules/scheduler": {
- "version": "0.19.1",
- "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz",
- "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==",
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz",
+ "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==",
"dev": true,
"dependencies": {
"loose-envify": "^1.1.0",
@@ -15055,7 +15065,8 @@
"node_modules/webpack-dev-server/node_modules/chokidar": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz",
- "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=",
+ "integrity": "sha512-mk8fAWcRUOxY7btlLtitj3A45jOwSAxH4tOFOoEGbVsl6cL6pPMWUy7dwZ/canfj3QEdP6FHSnf/l1c6/WkzVg==",
+ "deprecated": "Chokidar 2 will break on node v14+. Upgrade to chokidar 3 with 15x less dependencies.",
"dev": true,
"dependencies": {
"anymatch": "^1.3.0",
@@ -18102,20 +18113,20 @@
"dev": true
},
"chokidar": {
- "version": "3.5.1",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz",
- "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==",
+ "version": "3.5.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
+ "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
"dev": true,
"optional": true,
"requires": {
- "anymatch": "~3.1.1",
+ "anymatch": "~3.1.2",
"braces": "~3.0.2",
- "fsevents": "~2.3.1",
- "glob-parent": "~5.1.0",
+ "fsevents": "~2.3.2",
+ "glob-parent": "~5.1.2",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
"normalize-path": "~3.0.0",
- "readdirp": "~3.5.0"
+ "readdirp": "~3.6.0"
},
"dependencies": {
"anymatch": {
@@ -26073,15 +26084,14 @@
}
},
"react-dom": {
- "version": "16.14.0",
- "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz",
- "integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==",
+ "version": "17.0.2",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
+ "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==",
"dev": true,
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
- "prop-types": "^15.6.2",
- "scheduler": "^0.19.1"
+ "scheduler": "^0.20.2"
}
},
"react-error-overlay": {
@@ -26154,9 +26164,9 @@
}
},
"readdirp": {
- "version": "3.5.0",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz",
- "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==",
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"dev": true,
"optional": true,
"requires": {
@@ -26635,9 +26645,9 @@
"dev": true
},
"scheduler": {
- "version": "0.19.1",
- "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz",
- "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==",
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz",
+ "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==",
"dev": true,
"requires": {
"loose-envify": "^1.1.0",
@@ -28929,7 +28939,7 @@
"chokidar": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz",
- "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=",
+ "integrity": "sha512-mk8fAWcRUOxY7btlLtitj3A45jOwSAxH4tOFOoEGbVsl6cL6pPMWUy7dwZ/canfj3QEdP6FHSnf/l1c6/WkzVg==",
"dev": true,
"requires": {
"anymatch": "^1.3.0",
diff --git a/package.json b/package.json
index c98f56d..ddf8cbe 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "react-json-editor-viewer",
- "version": "1.0.7",
+ "version": "1.1.1",
"description": "React JSON Editor and JSON Viewer",
"main": "build/index.js",
"scripts": {
@@ -56,7 +56,7 @@
"raf": "3.4.0",
"react": "17.0.2",
"react-dev-utils": "^4.2.1",
- "react-dom": "^16.2.0",
+ "react-dom": "17.0.2",
"style-loader": "0.19.0",
"sw-precache-webpack-plugin": "0.11.4",
"url-loader": "0.6.2",
diff --git a/src/demo/App.js b/src/demo/App.js
index 17ab120..4c053fd 100644
--- a/src/demo/App.js
+++ b/src/demo/App.js
@@ -4,25 +4,66 @@ import { JSONEditor } from "../lib";
// to compile a production version
//npm publish to publish
-const App = () => (
-
-
Using default styles
-
+const App = () => {
+ const onJsonChange = (key, value, parent, data) => {
+ console.log(key, value, parent, data);
+ };
+ return (
+
+
Empty object
+ {" "}
+ Empty array
+ {" "}
+ Empty string
+ {" "}
+ A number
+ {" "}
+ A boolean
+ {" "}
+ Data
+
+ {/*
Using customized styles
-
-);
+ /> */}
+
+ );
+};
const styles = {
dualView: {
diff --git a/src/lib/JSONEditor.js b/src/lib/JSONEditor.js
index 7a4f04f..07a9704 100644
--- a/src/lib/JSONEditor.js
+++ b/src/lib/JSONEditor.js
@@ -19,7 +19,8 @@ import ParentLabel from "./components/editor/ParentLabel";
import Input from "./components/editor/Input";
import { EDIT_KEY } from "./util";
import { jsonEditorDefaultStyles } from "./util";
-import { getKey } from "./util";
+import { getKey, defaultValueByType } from "./util";
+import EmptyElement from "./components/editor/EmptyElement";
export default class JSONEditor extends React.Component {
static defaultProps = {
@@ -132,6 +133,7 @@ export default class JSONEditor extends React.Component {
//special case to avoid showing root
elems.push(
);
+ } else {
+ for (let key = 0; key < data.length; key++) {
+ this.recursiveParseData(
+ key,
+ parentKeyPath,
+ data,
+ elems,
+ marginLeft + marginLeftStep
+ );
+ }
}
} else if (isObject(data)) {
if (marginLeft > 0) {
//special case to avoid showing root
elems.push(
{
- this.recursiveParseData(
- key,
- parentKeyPath,
- data,
- elems,
- marginLeft + marginLeftStep
+ const fields = Object.keys(data);
+ if (fields.length === 0 && marginLeft === 0) {
+ elems.push(
+
);
- });
+ } else {
+ fields.forEach((key) => {
+ this.recursiveParseData(
+ key,
+ parentKeyPath,
+ data,
+ elems,
+ marginLeft + marginLeftStep
+ );
+ });
+ }
} else if (isNumber(data)) {
elems.push(
{
+ addElement = (parent, type = "text") => {
let newKey = null;
if (isArray(parent)) {
- parent.push("");
+ parent.push(defaultValueByType(type));
newKey = parent.length - 1;
} else {
newKey = EDIT_KEY;
- parent[newKey] = "";
+ parent[newKey] = defaultValueByType(type);
}
this.setState({ data: this.state.data });
if (this.props.onChange)
@@ -277,11 +307,35 @@ export default class JSONEditor extends React.Component {
this.props.onChange(removeKey, currentValue, parent, this.state.data);
};
- saveElement = (parent, saveKey) => {
+ saveElement = (parent, saveKey, type, newValue) => {
let value = parent[EDIT_KEY];
+
+ // if type exists then it is a new item
+ if (type === "object") {
+ value = {};
+ }
+ if (type === "array") {
+ value = [];
+ }
+ if (type === "null") {
+ value = null;
+ }
+ if (type === "number") {
+ try {
+ value = parseFloat(newValue);
+ } catch (error) {
+ value = 0;
+ }
+ }
+ if (type === "boolean") {
+ value = newValue === "true";
+ }
+ // else string
+
parent[saveKey] = value;
delete parent[EDIT_KEY];
this.setState(this.state.data);
+
if (this.props.onChange)
this.props.onChange(saveKey, value, parent, this.state.data);
};
diff --git a/src/lib/components/editor/AddIcon.js b/src/lib/components/editor/AddIcon.js
index 013a10c..9eaeebe 100644
--- a/src/lib/components/editor/AddIcon.js
+++ b/src/lib/components/editor/AddIcon.js
@@ -1,12 +1,32 @@
import React from "react";
+import PropTypes from "prop-types";
const AddIcon = (props) => {
- let { addElement, addTo, hidden, styles } = props;
+ let { addElement, addTo, hidden, styles, type } = props;
return (
- addElement(addTo)}>
+ addElement(addTo, type)}>
+
);
};
+AddIcon.displayName = "AddIcon";
+
+AddIcon.propTypes = {
+ type: PropTypes.oneOf([
+ "text",
+ "object",
+ "array",
+ "boolean",
+ "number",
+ "null",
+ ]),
+ addElement: PropTypes.func.isRequired,
+ addTo: PropTypes.any.isRequired,
+ hidden: PropTypes.bool,
+ styles: PropTypes.shape({
+ addButton: PropTypes.object,
+ }).isRequired,
+};
+
export default AddIcon;
diff --git a/src/lib/components/editor/EmptyElement.js b/src/lib/components/editor/EmptyElement.js
new file mode 100644
index 0000000..9f5f814
--- /dev/null
+++ b/src/lib/components/editor/EmptyElement.js
@@ -0,0 +1,41 @@
+import React from "react";
+import merge from "lodash/merge";
+import Label from "./Label";
+import AddIcon from "./AddIcon";
+import TypeSelector from "./TypeSelector";
+
+export default class EmptyElement extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ type: "text",
+ };
+ }
+
+ onTypeChanged = (event) => {
+ this.setState((state) => ({ ...state, type: event.target.value }));
+ };
+
+ render() {
+ const { type } = this.state;
+ const { marginLeft, marginBottom, label, addElement, current, styles } =
+ this.props;
+ const style = merge({ marginLeft, marginBottom }, styles.row);
+ return (
+
+ );
+ }
+}
diff --git a/src/lib/components/editor/Input.js b/src/lib/components/editor/Input.js
index f41e1fb..596c81a 100644
--- a/src/lib/components/editor/Input.js
+++ b/src/lib/components/editor/Input.js
@@ -4,6 +4,7 @@ import RemoveIcon from "./RemoveIcon";
import SaveIcon from "./SaveIcon";
import { EDIT_KEY } from "../../util";
import Label from "./Label";
+import TypeSelector from "./TypeSelector";
export default class Input extends React.Component {
constructor(props) {
@@ -11,6 +12,8 @@ export default class Input extends React.Component {
this.state = {
hovering: false,
editableInput: null,
+ type: props.type,
+ value: props.value,
};
}
@@ -26,10 +29,22 @@ export default class Input extends React.Component {
this.setState({ editableInput: event.target.value });
};
+ onTypeChanged = (event) => {
+ this.setState((state) => ({ ...state, type: event.target.value }));
+ };
+
+ onValueChanged = (event) => {
+ this.setState({ value: event.target.value }, () => {
+ if (this.props.label !== EDIT_KEY || this.state.type === "text") {
+ this.props.onChange(event);
+ }
+ });
+ };
+
onSaveElement = () => {
let { saveElement, parent } = this.props;
- let { editableInput } = this.state;
- saveElement(parent, editableInput);
+ let { editableInput, type, value } = this.state;
+ saveElement(parent, editableInput, type, value);
};
render() {
@@ -37,9 +52,6 @@ export default class Input extends React.Component {
marginLeft,
marginBottom,
label,
- value,
- type,
- onChange,
removeElement,
parent,
currentKey,
@@ -47,6 +59,7 @@ export default class Input extends React.Component {
styles,
} = this.props;
let style = merge({ marginLeft, marginBottom }, styles.row);
+ const { type, value } = this.state;
return (
+
+
+
-
+ {type === "object" && {"{}"}}
+ {type === "array" && {"[]"}}
+ {type === "null" && {"null"}}
+ {type === "boolean" && (
+
+ )}
+ {(type === "text" || type === "number") && (
+
+ )}
{
+ this.setState((state) => ({ ...state, type: event.target.value }));
+ };
hoverStarted = () => {
this.setState({ hovering: true });
@@ -20,7 +26,7 @@ export default class ParentLabel extends React.Component {
};
render() {
- let {
+ const {
marginLeft,
value,
currentKey,
@@ -32,11 +38,13 @@ export default class ParentLabel extends React.Component {
showRemoveButton,
showAddButton,
styles,
+ parentType,
} = this.props;
- let style = merge(
- { marginLeft: marginLeft, display: "flex" },
+ const style = merge(
+ { marginLeft: marginLeft, display: "flex", alignItems: "baseline" },
styles.label
);
+ const { type, hovering } = this.state;
return (
{getCollapseIcon(marginLeft, currentKey)}
-
+ {parentType === "array" && (
+
+ )}
+
+
+
+
+ );
+ }
+}
+
+TypeSelector.propTypes = {
+ type: PropTypes.oneOf([
+ "text",
+ "object",
+ "array",
+ "boolean",
+ "number",
+ "null",
+ ]).isRequired,
+ onChange: PropTypes.func.isRequired,
+ hidden: PropTypes.bool,
+ styles: PropTypes.shape({
+ typeSelect: PropTypes.object,
+ select: PropTypes.object,
+ }).isRequired,
+};
diff --git a/src/lib/util.js b/src/lib/util.js
index aefd36f..9234988 100644
--- a/src/lib/util.js
+++ b/src/lib/util.js
@@ -28,6 +28,7 @@ export const jsonEditorDefaultStyles = {
},
row: {
display: "flex",
+ alignItems: "baseline",
},
root: {
fontSize: 14,
@@ -40,6 +41,11 @@ export const jsonEditorDefaultStyles = {
borderRadius: 3,
borderColor: "#d3d3d3",
},
+ typeSelect: {
+ marginLeft: 10,
+ marginTop: 4,
+ marginBottom: 4,
+ },
input: {
borderRadius: 3,
border: "1px solid #d3d3d3",
@@ -102,3 +108,21 @@ export const jsonViewerDefaultStyles = {
cursor: "pointer",
},
};
+
+export const defaultValueByType = (type = "text") => {
+ switch (type) {
+ case "object":
+ return {};
+ case "array":
+ return [];
+ case "boolean":
+ return false;
+ case "number":
+ return 0;
+ case "null":
+ return null;
+ case "text": // fall-through
+ default:
+ return "";
+ }
+};