From af5653c7829a62cc2ff84f6f1f8fac99dd0cb273 Mon Sep 17 00:00:00 2001 From: Carl Smith <5456533+CarlosNZ@users.noreply.github.com> Date: Sun, 16 Jun 2024 14:21:37 +1200 Subject: [PATCH 1/8] onError implementation --- README.md | 1 + demo/src/App.tsx | 2 + demo/src/_imports.ts | 4 +- demo/src/demoData/dataDefinitions.tsx | 7 +++ src/CollectionNode.tsx | 64 ++++++++++++++++++++------- src/JsonEditor.tsx | 4 ++ src/ValueNodeWrapper.tsx | 46 ++++++++++++++----- src/types.ts | 18 ++++++++ 8 files changed, 116 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 4b750f32..91bb4652 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ Features include: - self-contained — rendered with plain HTML/CSS, so no dependance on external UI libraries - search/filter data by key, value or custom function - provide your own [custom component](#custom-nodes) to integrate specialised UI for certain data. + - [localisable](#localisation) UI **[Explore the Demo](https://carlosnz.github.io/json-edit-react/)** diff --git a/demo/src/App.tsx b/demo/src/App.tsx index baef0ede..c5cfbffc 100644 --- a/demo/src/App.tsx +++ b/demo/src/App.tsx @@ -260,6 +260,8 @@ function App() { } : undefined } + onError={demoData[selectedData].onError} + showErrorMessages={demoData[selectedData].showErrorMessages} collapse={collapseLevel} showCollectionCount={ showCount === 'Yes' ? true : showCount === 'When closed' ? 'when-closed' : false diff --git a/demo/src/_imports.ts b/demo/src/_imports.ts index 951d5913..c39e3044 100644 --- a/demo/src/_imports.ts +++ b/demo/src/_imports.ts @@ -3,10 +3,10 @@ */ /* Installed package */ -export * from 'json-edit-react' +// export * from 'json-edit-react' /* Local src */ -// export * from './json-edit-react/src' +export * from './json-edit-react/src' /* Compiled local package */ // export * from './package/build' diff --git a/demo/src/demoData/dataDefinitions.tsx b/demo/src/demoData/dataDefinitions.tsx index 2f6f4b71..85c63276 100644 --- a/demo/src/demoData/dataDefinitions.tsx +++ b/demo/src/demoData/dataDefinitions.tsx @@ -15,6 +15,7 @@ import { DataType, DefaultValueFunction, OnChangeFunction, + OnErrorFunction, SearchFilterFunction, ThemeStyles, UpdateFunction, @@ -51,6 +52,8 @@ interface DemoData { path: CollectionKey[] }) => any onChange?: OnChangeFunction + onError?: OnErrorFunction + showErrorMessages?: boolean defaultValue?: unknown | DefaultValueFunction customNodeDefinitions?: CustomNodeDefinition[] customTextDefinitions?: CustomTextDefinitions @@ -459,6 +462,10 @@ export const demoData: Record = { restrictEdit: ({ level }) => level > 0, restrictAdd: true, restrictDelete: true, + onError: (data) => { + console.log('Data', data) + }, + showErrorMessages: false, customNodeDefinitions: [ { condition: ({ key, value }) => diff --git a/src/CollectionNode.tsx b/src/CollectionNode.tsx index 994d32e9..bc7fbb90 100644 --- a/src/CollectionNode.tsx +++ b/src/CollectionNode.tsx @@ -7,7 +7,9 @@ import { type CollectionNodeProps, type ErrorString, type NodeData, + type JerError, ERROR_DISPLAY_TIME, + CollectionData, } from './types' import { Icon } from './Icons' import { filterNode, isCollection } from './filterHelpers' @@ -16,6 +18,7 @@ import { AutogrowTextArea } from './AutogrowTextArea' import { useTheme } from './theme' import { useTreeState } from './TreeStateProvider' import { toPathString } from './ValueNodes' +import extractProperty from 'object-property-extractor' export const CollectionNode: React.FC = ({ data, @@ -37,6 +40,8 @@ export const CollectionNode: React.FC = ({ onEdit, onAdd, onDelete, + onError: onErrorCallback, + showErrorMessages, restrictEditFilter, restrictDeleteFilter, restrictAddFilter, @@ -112,6 +117,31 @@ export const CollectionNode: React.FC = ({ showCollectionWrapper = true, } = useMemo(() => getCustomNode(customNodeDefinitions, nodeData), []) + const showError = (errorString: ErrorString) => { + if (showErrorMessages) { + setError(errorString) + setTimeout(() => setError(null), ERROR_DISPLAY_TIME) + } + console.warn('Error', errorString) + } + + const onError = useMemo( + () => (error: JerError, errorValue: CollectionData | string) => { + showError(error.error) + if (onErrorCallback) { + onErrorCallback({ + currentData: nodeData.fullData, + errorValue, + currentValue: data, + name, + path, + error, + }) + } + }, + [onErrorCallback, showErrorMessages] + ) + // Early return if this node is filtered out if (!filterNode('collection', nodeData, searchFilter, searchText) && nodeData.level > 0) { return null @@ -144,12 +174,6 @@ export const CollectionNode: React.FC = ({ } } - const showError = (errorString: ErrorString) => { - setError(errorString) - setTimeout(() => setError(null), ERROR_DISPLAY_TIME) - console.log('Error', errorString) - } - const handleEdit = () => { try { const value = JSON5.parse(stringifiedValue) @@ -157,12 +181,15 @@ export const CollectionNode: React.FC = ({ setError(null) if (JSON.stringify(value) === JSON.stringify(data)) return onEdit(value, path).then((error) => { - if (error) showError(error) + if (error) { + onError({ code: 'UPDATE_ERROR', error }, value as CollectionData) + } }) } catch { - setError(translate('ERROR_INVALID_JSON', nodeData)) - setTimeout(() => setError(null), ERROR_DISPLAY_TIME) - console.log('Invalid JSON') + onError( + { code: 'INVALID_JSON', error: translate('ERROR_INVALID_JSON', nodeData) }, + stringifiedValue + ) } } @@ -173,7 +200,7 @@ export const CollectionNode: React.FC = ({ const parentPath = path.slice(0, -1) const existingKeys = Object.keys(parentData) if (existingKeys.includes(newKey)) { - showError(translate('ERROR_KEY_EXISTS', nodeData)) + onError({ code: 'KEY_EXISTS', error: translate('ERROR_KEY_EXISTS', nodeData) }, newKey) return } @@ -194,13 +221,13 @@ export const CollectionNode: React.FC = ({ const newValue = getDefaultNewValue(nodeData) if (collectionType === 'array') { onAdd(newValue, [...path, (data as unknown[]).length]).then((error) => { - if (error) showError(error) + if (error) onError({ code: 'ADD_ERROR', error }, newValue as CollectionData) }) } else if (key in data) { - showError(translate('ERROR_KEY_EXISTS', nodeData)) + onError({ code: 'KEY_EXISTS', error: translate('ERROR_KEY_EXISTS', nodeData) }, key) } else { onAdd(newValue, [...path, key]).then((error) => { - if (error) showError(error) + if (error) onError({ code: 'ADD_ERROR', error }, newValue as CollectionData) }) } } @@ -208,8 +235,13 @@ export const CollectionNode: React.FC = ({ const handleDelete = path.length > 0 ? () => { - onDelete(data, path).then((result) => { - if (result) showError(result) + onDelete(data, path).then((error) => { + if (error) { + onError( + { code: 'DELETE_ERROR', error }, + extractProperty(data, path) as CollectionData + ) + } }) } : undefined diff --git a/src/JsonEditor.tsx b/src/JsonEditor.tsx index edff27e4..6dcbde59 100644 --- a/src/JsonEditor.tsx +++ b/src/JsonEditor.tsx @@ -27,6 +27,8 @@ const Editor: React.FC = ({ onDelete: srcDelete = onUpdate, onAdd: srcAdd = onUpdate, onChange, + onError, + showErrorMessages = true, enableClipboard = true, theme = 'default', icons, @@ -174,6 +176,8 @@ const Editor: React.FC = ({ onDelete, onAdd, onChange, + onError, + showErrorMessages, showCollectionCount, collapseFilter, restrictEditFilter, diff --git a/src/ValueNodeWrapper.tsx b/src/ValueNodeWrapper.tsx index 08ca6c89..68e85874 100644 --- a/src/ValueNodeWrapper.tsx +++ b/src/ValueNodeWrapper.tsx @@ -20,6 +20,7 @@ import { type CollectionData, type ErrorString, type ValueData, + type JerError, } from './types' import { useTheme } from './theme' import './style.css' @@ -35,6 +36,8 @@ export const ValueNodeWrapper: React.FC = (props) => { onEdit, onDelete, onChange, + onError: onErrorCallback, + showErrorMessages, enableClipboard, restrictEditFilter, restrictDeleteFilter, @@ -90,6 +93,31 @@ export const ValueNodeWrapper: React.FC = (props) => { const canEdit = useMemo(() => !restrictEditFilter(nodeData), [nodeData]) const canDelete = useMemo(() => !restrictDeleteFilter(nodeData), [nodeData]) + const showError = (errorString: ErrorString) => { + if (showErrorMessages) { + setError(errorString) + setTimeout(() => setError(null), ERROR_DISPLAY_TIME) + } + console.warn('Error', errorString) + } + + const onError = useMemo( + () => (error: JerError, errorValue: ValueData) => { + showError(error.error) + if (onErrorCallback) { + onErrorCallback({ + currentData: nodeData.fullData, + errorValue, + currentValue: value as ValueData, + name, + path, + error, + }) + } + }, + [onErrorCallback, showErrorMessages] + ) + const { CustomNode, customNodeProps, @@ -143,15 +171,9 @@ export const ValueNodeWrapper: React.FC = (props) => { } } - const logError = (errorString: ErrorString) => { - setError(errorString) - setTimeout(() => setError(null), ERROR_DISPLAY_TIME) - console.log('Error', errorString) - } - const handleEdit = () => { setCurrentlyEditingElement(null) - let newValue + let newValue: ValueData | CollectionData switch (dataType) { case 'object': newValue = { [translate('DEFAULT_NEW_KEY', nodeData)]: value } @@ -169,7 +191,7 @@ export const ValueNodeWrapper: React.FC = (props) => { newValue = value } onEdit(newValue, path).then((error) => { - if (error) logError(error) + if (error) onError({ code: 'UPDATE_ERROR', error }, newValue as ValueData) }) } @@ -180,7 +202,7 @@ export const ValueNodeWrapper: React.FC = (props) => { const parentPath = path.slice(0, -1) const existingKeys = Object.keys(parentData) if (existingKeys.includes(newKey)) { - logError(translate('ERROR_KEY_EXISTS', nodeData)) + onError({ code: 'KEY_EXISTS', error: translate('ERROR_KEY_EXISTS', nodeData) }, newKey) return } @@ -204,7 +226,7 @@ export const ValueNodeWrapper: React.FC = (props) => { const handleDelete = () => { onDelete(value, path).then((error) => { - if (error) logError(error) + if (error) onError({ code: 'DELETE_ERROR', error }, value as ValueData) }) } @@ -213,7 +235,7 @@ export const ValueNodeWrapper: React.FC = (props) => { const isEditingKey = currentlyEditingElement === `key_${pathString}` const isArray = typeof path.slice(-1)[0] === 'number' const canEditKey = !isArray && canEdit && canDelete - const showError = !isEditing && error + const showErrorString = !isEditing && error const showTypeSelector = isEditing && allowedDataTypes.length > 0 const showEditButtons = dataType !== 'invalid' && !error && showEditTools const showKeyEdit = showLabel && isEditingKey @@ -329,7 +351,7 @@ export const ValueNodeWrapper: React.FC = (props) => { )} - {showError && ( + {showErrorString && ( {error} diff --git a/src/types.ts b/src/types.ts index ac013b82..3da47fe5 100644 --- a/src/types.ts +++ b/src/types.ts @@ -11,6 +11,8 @@ export interface JsonEditorProps { onDelete?: UpdateFunction onAdd?: UpdateFunction onChange?: OnChangeFunction + onError?: OnErrorFunction + showErrorMessages?: boolean enableClipboard?: boolean | CopyFunction theme?: ThemeInput icons?: IconReplacements @@ -87,6 +89,20 @@ export type OnChangeFunction = (props: { path: CollectionKey[] }) => ValueData +export interface JerError { + code: 'UPDATE_ERROR' | 'DELETE_ERROR' | 'ADD_ERROR' | 'INVALID_JSON' | 'KEY_EXISTS' + error: ErrorString +} + +export type OnErrorFunction = (props: { + currentData: object + errorValue: ValueData | CollectionData + currentValue: ValueData | CollectionData + name: CollectionKey + path: CollectionKey[] + error: JerError +}) => void + export type FilterFunction = (input: NodeData) => boolean export type TypeFilterFunction = (input: NodeData) => boolean | DataType[] export type CustomTextFunction = (input: NodeData) => string | null @@ -135,6 +151,8 @@ interface BaseNodeProps { nodeData: NodeData onEdit: InternalUpdateFunction onDelete: InternalUpdateFunction + onError?: OnErrorFunction + showErrorMessages: boolean enableClipboard: boolean | CopyFunction restrictEditFilter: FilterFunction restrictDeleteFilter: FilterFunction From 53ed73f89df143c70bf77f9266506f3e3d2731f8 Mon Sep 17 00:00:00 2001 From: Carl Smith <5456533+CarlosNZ@users.noreply.github.com> Date: Sun, 16 Jun 2024 15:17:17 +1200 Subject: [PATCH 2/8] Update Demo and docs --- README.md | 25 ++++++++++++++++++++++++- demo/src/App.tsx | 18 ++++++++++++++++-- demo/src/demoData/dataDefinitions.tsx | 12 ++++++++---- src/CollectionNode.tsx | 16 ++++++++-------- src/ValueNodeWrapper.tsx | 8 ++++---- src/index.ts | 4 ++++ src/types.ts | 4 ++-- 7 files changed, 66 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 91bb4652..60d2e3a4 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ Features include: - [Props overview](#props-overview) - [Update functions](#update-functions) - [OnChange function](#onchange-function) + - [OnError function](#onerror-function) - [Copy function](#copy-function) - [Filter functions](#filter-functions) - [Examples](#examples-1) @@ -97,6 +98,8 @@ The only *required* value is `data`. | `onDelete` | `UpdateFunction` | | A function to run whenever a value is **deleted**. | | `onAdd` | `UpdateFunction` | | A function to run whenever a new property is **added**. | | `onChange` | `OnChangeFunction` | | A function to modify/constrain user input as they type -- see [OnChange functions](#onchange-function). | +| `onError` | `OnErrorFunction` | | A function to run whenever the component reports an error -- see [OnErrorFunction](#onerror-function). | +| `showErrorMessages` | `boolean ` | `true` | Whether or not the component should display its own error messages (you'd probably only want to disable this if you provided your own `onError` function) | | `enableClipboard` | `boolean\|CopyFunction` | `true` | Whether or not to enable the "Copy to clipboard" button in the UI. If a function is provided, `true` is assumed and this function will be run whenever an item is copied. | | `indent` | `number` | `3` | Specify the amount of indentation for each level of nesting in the displayed data. | | `collapse` | `boolean\|number\|FilterFunction` | `false` | Defines which nodes of the JSON tree will be displayed "opened" in the UI on load. If `boolean`, it'll be either all or none. A `number` specifies a nesting depth after which nodes will be closed. For more fine control a function can be provided — see [Filter functions](#filter-functions). | @@ -170,6 +173,26 @@ The input object is similar to the Update function input, but with no `newData` } ``` +### OnError function + +Normally, the component will display simple error messages whenever an error condition is detected (e.g. invalid JSON input, duplicate keys, or custom errors returned by the [`onUpdate` functions)](#update-functions)). However, you can provide your own `onError` callback in order to implement your own error UI, or run additional side effects. (In the former case, you'd probably want to disable the `showErrorMessages` prop, too.) The input to the callback is similar to the other callbacks: + +```js +{ + currentData, // data state before update + currentValue, // the current value of the property being updated + errorValue, // the erroneous value that failed to update the property + name, // name of the property being updated + path, // full path to the property being updated, as an array of property keys + // (e.g. [ "user", "friends", 1, "name" ] ) (equivalent to "user.friends[1].name"), + error: { + code, // one of 'UPDATE_ERROR' | 'DELETE_ERROR' | 'ADD_ERROR' | 'INVALID_JSON' | 'KEY_EXISTS' + message // the (localised) error message that would be displayed + } +} +``` + (An example of a custom Error UI can be seen in the [Demo](#https://carlosnz.github.io/json-edit-react/) with the "Custom Nodes" data set -- when you enter invalid JSON input a "Toast" notification is displayed instead of the normal component error message.) + ### Copy function A similar callback is executed whenever an item is copied to the clipboard (if passed to the `enableClipboard` prop), but with a different input parameter: @@ -561,7 +584,7 @@ A few helper functions, components and types that might be useful in your own im - `Theme`: a full [Theme](#themes--styles) object - `ThemeInput`: input type for the `theme` prop - `JsonEditorProps`: all input props for the Json Editor component -- [`UpdateFunction`](#update-functions), [`OnChangeFunction`](#onchange-function), [`FilterFunction`](#filter-functions), [`CopyFunction`](#copy-function), [`SearchFilterFunction`](#searchfiltering), [`CompareFunction`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort), [`LocalisedString`](#localisation), [`CustomNodeDefinition`](#custom-nodes), [`CustomTextDefinitions`](#custom-text) +- [`UpdateFunction`](#update-functions), [`OnChangeFunction`](#onchange-function), [`OnErrorFunction`](#onerror-function) [`FilterFunction`](#filter-functions), [`CopyFunction`](#copy-function), [`SearchFilterFunction`](#searchfiltering), [`CompareFunction`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort), [`LocalisedString`](#localisation), [`CustomNodeDefinition`](#custom-nodes), [`CustomTextDefinitions`](#custom-text) - `TranslateFunction`: function that takes a [localisation](#localisation) key and returns a translated string - `IconReplacements`: input type for the `icons` prop - `CollectionNodeProps`: all props passed internally to "collection" nodes (i.e. objects/arrays) diff --git a/demo/src/App.tsx b/demo/src/App.tsx index c5cfbffc..18ebe0d8 100644 --- a/demo/src/App.tsx +++ b/demo/src/App.tsx @@ -33,10 +33,11 @@ import { } from '@chakra-ui/react' import logo from './image/logo_400.png' import { ArrowBackIcon, ArrowForwardIcon } from '@chakra-ui/icons' -import { demoData } from './demoData' +import { DemoData, demoData } from './demoData' import { useDatabase } from './useDatabase' import './style.css' import { version } from './version' +import { OnErrorFunction } from './json-edit-react/src/types' function App() { const [selectedData, setSelectedData] = useState('intro') @@ -260,7 +261,20 @@ function App() { } : undefined } - onError={demoData[selectedData].onError} + onError={ + demoData[selectedData].onError + ? (errorData) => { + const error = (demoData[selectedData].onError as OnErrorFunction)(errorData) + toast({ + title: 'ERROR 😢', + description: error as any, + status: 'error', + duration: 5000, + isClosable: true, + }) + } + : undefined + } showErrorMessages={demoData[selectedData].showErrorMessages} collapse={collapseLevel} showCollectionCount={ diff --git a/demo/src/demoData/dataDefinitions.tsx b/demo/src/demoData/dataDefinitions.tsx index 85c63276..c0cc0091 100644 --- a/demo/src/demoData/dataDefinitions.tsx +++ b/demo/src/demoData/dataDefinitions.tsx @@ -22,7 +22,7 @@ import { } from '../json-edit-react/src/types' import { Input } from 'object-property-assigner/build' -interface DemoData { +export interface DemoData { name: string description: JSX.Element data: object @@ -431,6 +431,12 @@ export const demoData: Record = { In this example, compare the raw JSON (edit the data root) with what is presented here. + (You can also see a custom{' '} + + onError + {' '} + function that displays a Toast notification rather than the standard error message when + you enter invalid JSON input.) You can also see how the property count text changes depending on the data. This is using @@ -462,9 +468,7 @@ export const demoData: Record = { restrictEdit: ({ level }) => level > 0, restrictAdd: true, restrictDelete: true, - onError: (data) => { - console.log('Data', data) - }, + onError: (errorData) => errorData.error.message, showErrorMessages: false, customNodeDefinitions: [ { diff --git a/src/CollectionNode.tsx b/src/CollectionNode.tsx index bc7fbb90..78f0d4ec 100644 --- a/src/CollectionNode.tsx +++ b/src/CollectionNode.tsx @@ -127,7 +127,7 @@ export const CollectionNode: React.FC = ({ const onError = useMemo( () => (error: JerError, errorValue: CollectionData | string) => { - showError(error.error) + showError(error.message) if (onErrorCallback) { onErrorCallback({ currentData: nodeData.fullData, @@ -182,12 +182,12 @@ export const CollectionNode: React.FC = ({ if (JSON.stringify(value) === JSON.stringify(data)) return onEdit(value, path).then((error) => { if (error) { - onError({ code: 'UPDATE_ERROR', error }, value as CollectionData) + onError({ code: 'UPDATE_ERROR', message: error }, value as CollectionData) } }) } catch { onError( - { code: 'INVALID_JSON', error: translate('ERROR_INVALID_JSON', nodeData) }, + { code: 'INVALID_JSON', message: translate('ERROR_INVALID_JSON', nodeData) }, stringifiedValue ) } @@ -200,7 +200,7 @@ export const CollectionNode: React.FC = ({ const parentPath = path.slice(0, -1) const existingKeys = Object.keys(parentData) if (existingKeys.includes(newKey)) { - onError({ code: 'KEY_EXISTS', error: translate('ERROR_KEY_EXISTS', nodeData) }, newKey) + onError({ code: 'KEY_EXISTS', message: translate('ERROR_KEY_EXISTS', nodeData) }, newKey) return } @@ -221,13 +221,13 @@ export const CollectionNode: React.FC = ({ const newValue = getDefaultNewValue(nodeData) if (collectionType === 'array') { onAdd(newValue, [...path, (data as unknown[]).length]).then((error) => { - if (error) onError({ code: 'ADD_ERROR', error }, newValue as CollectionData) + if (error) onError({ code: 'ADD_ERROR', message: error }, newValue as CollectionData) }) } else if (key in data) { - onError({ code: 'KEY_EXISTS', error: translate('ERROR_KEY_EXISTS', nodeData) }, key) + onError({ code: 'KEY_EXISTS', message: translate('ERROR_KEY_EXISTS', nodeData) }, key) } else { onAdd(newValue, [...path, key]).then((error) => { - if (error) onError({ code: 'ADD_ERROR', error }, newValue as CollectionData) + if (error) onError({ code: 'ADD_ERROR', message: error }, newValue as CollectionData) }) } } @@ -238,7 +238,7 @@ export const CollectionNode: React.FC = ({ onDelete(data, path).then((error) => { if (error) { onError( - { code: 'DELETE_ERROR', error }, + { code: 'DELETE_ERROR', message: error }, extractProperty(data, path) as CollectionData ) } diff --git a/src/ValueNodeWrapper.tsx b/src/ValueNodeWrapper.tsx index 68e85874..c16e8e12 100644 --- a/src/ValueNodeWrapper.tsx +++ b/src/ValueNodeWrapper.tsx @@ -103,7 +103,7 @@ export const ValueNodeWrapper: React.FC = (props) => { const onError = useMemo( () => (error: JerError, errorValue: ValueData) => { - showError(error.error) + showError(error.message) if (onErrorCallback) { onErrorCallback({ currentData: nodeData.fullData, @@ -191,7 +191,7 @@ export const ValueNodeWrapper: React.FC = (props) => { newValue = value } onEdit(newValue, path).then((error) => { - if (error) onError({ code: 'UPDATE_ERROR', error }, newValue as ValueData) + if (error) onError({ code: 'UPDATE_ERROR', message: error }, newValue as ValueData) }) } @@ -202,7 +202,7 @@ export const ValueNodeWrapper: React.FC = (props) => { const parentPath = path.slice(0, -1) const existingKeys = Object.keys(parentData) if (existingKeys.includes(newKey)) { - onError({ code: 'KEY_EXISTS', error: translate('ERROR_KEY_EXISTS', nodeData) }, newKey) + onError({ code: 'KEY_EXISTS', message: translate('ERROR_KEY_EXISTS', nodeData) }, newKey) return } @@ -226,7 +226,7 @@ export const ValueNodeWrapper: React.FC = (props) => { const handleDelete = () => { onDelete(value, path).then((error) => { - if (error) onError({ code: 'DELETE_ERROR', error }, value as ValueData) + if (error) onError({ code: 'DELETE_ERROR', message: error }, value as ValueData) }) } diff --git a/src/index.ts b/src/index.ts index 49d6d871..3c81f4d0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,6 +3,8 @@ import { type JsonEditorProps, type UpdateFunction, type OnChangeFunction, + type OnErrorFunction, + type JerError, type CopyFunction, type FilterFunction, type SearchFilterFunction, @@ -36,6 +38,8 @@ export { type JsonEditorProps, type UpdateFunction, type OnChangeFunction, + type OnErrorFunction, + type JerError, type CopyFunction, type FilterFunction, type SearchFilterFunction, diff --git a/src/types.ts b/src/types.ts index 3da47fe5..5f0b40ee 100644 --- a/src/types.ts +++ b/src/types.ts @@ -91,7 +91,7 @@ export type OnChangeFunction = (props: { export interface JerError { code: 'UPDATE_ERROR' | 'DELETE_ERROR' | 'ADD_ERROR' | 'INVALID_JSON' | 'KEY_EXISTS' - error: ErrorString + message: ErrorString } export type OnErrorFunction = (props: { @@ -101,7 +101,7 @@ export type OnErrorFunction = (props: { name: CollectionKey path: CollectionKey[] error: JerError -}) => void +}) => unknown export type FilterFunction = (input: NodeData) => boolean export type TypeFilterFunction = (input: NodeData) => boolean | DataType[] From d4991c0a3db01a49ba2d8318490dfb32e4b0e4e1 Mon Sep 17 00:00:00 2001 From: Carl Smith <5456533+CarlosNZ@users.noreply.github.com> Date: Sun, 16 Jun 2024 15:29:25 +1200 Subject: [PATCH 3/8] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 60d2e3a4..bdcb566d 100644 --- a/README.md +++ b/README.md @@ -608,6 +608,9 @@ This component is heavily inspired by [react-json-view](https://github.com/mac-s ## Changelog +- **1.12.0**: + - Preserve editing mode when changing Data Type + - [`onError` callback](#onerror-function) available for custom error handling - **1.11.8**: Fix regression for empty data root name introduces in 1.11.7 - **1.11.7**: Handle \ object keys / prevent duplicate keys - **1.11.3**: Bug fix for invalid state when changing type to Collection node From 82da1fe833e6f94e4426f497f7569a42370f0bdd Mon Sep 17 00:00:00 2001 From: Carl Smith <5456533+CarlosNZ@users.noreply.github.com> Date: Sun, 16 Jun 2024 15:29:53 +1200 Subject: [PATCH 4/8] v1.12.0-rc1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2a7ff6bb..636c94e2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "json-edit-react", - "version": "1.11.9", + "version": "1.12.0-rc1", "description": "React component for editing or viewing JSON/object data", "main": "build/index.cjs.js", "module": "build/index.esm.js", From 8ad283ba622a46dd4dbe361f67b42c3418426e1a Mon Sep 17 00:00:00 2001 From: Carl Smith <5456533+CarlosNZ@users.noreply.github.com> Date: Sun, 16 Jun 2024 15:41:07 +1200 Subject: [PATCH 5/8] Update demo --- demo/package.json | 2 +- demo/src/version.ts | 2 +- demo/yarn.lock | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/demo/package.json b/demo/package.json index b7b92034..5db9cae2 100644 --- a/demo/package.json +++ b/demo/package.json @@ -17,7 +17,7 @@ "@types/react-dom": "^18.2.18", "firebase": "^10.7.2", "framer-motion": "^11.0.3", - "json-edit-react": "^1.11.9", + "json-edit-react": "^1.12.0", "just-compare": "^2.3.0", "react": "^18.2.0", "react-datepicker": "^5.0.0", diff --git a/demo/src/version.ts b/demo/src/version.ts index 9aa624bb..22736a18 100644 --- a/demo/src/version.ts +++ b/demo/src/version.ts @@ -1 +1 @@ -export const version = '1.11.9' \ No newline at end of file +export const version = '1.12.0-rc1' \ No newline at end of file diff --git a/demo/yarn.lock b/demo/yarn.lock index aed5e9c6..9133fb35 100644 --- a/demo/yarn.lock +++ b/demo/yarn.lock @@ -7965,10 +7965,10 @@ json-buffer@3.0.1: resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== -json-edit-react@^1.11.9: - version "1.11.9" - resolved "https://registry.yarnpkg.com/json-edit-react/-/json-edit-react-1.11.9.tgz#9057940f4bc6e50d057238811f80d3beedec532c" - integrity sha512-uhRWiJzgf8RGvcc75+lq/LNpTCw6jxM62DHN4xS3JJOuoR6AoECbKsj4xcz5i/aWs6f19OkDn6DJ9syv9VD03w== +json-edit-react@^1.12.0: + version "1.12.0-rc1" + resolved "https://registry.yarnpkg.com/json-edit-react/-/json-edit-react-1.12.0-rc1.tgz#878338843fd872e136448c985eb9fb25ca9590a8" + integrity sha512-NzREei+lEtshSzSfiy/SkQz9P9CpnnJzCxTKTW7uhEo6M+y7f9cSiAh9uTWTeonO7SQH0VuShesPzmayEcQnCA== dependencies: json5 "^2.2.3" just-clone "^6.2.0" From e65f3cd43ca807823e032726a208e8d84bf97475 Mon Sep 17 00:00:00 2001 From: Carl Smith <5456533+CarlosNZ@users.noreply.github.com> Date: Mon, 17 Jun 2024 08:19:54 +1200 Subject: [PATCH 6/8] JSON5 option --- README.md | 76 ++++++++++++++++++++++-------------------- demo/src/App.tsx | 1 + src/CollectionNode.tsx | 17 +++++++--- src/JsonEditor.tsx | 2 ++ src/types.ts | 13 ++++++++ 5 files changed, 68 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index bdcb566d..acb59590 100644 --- a/README.md +++ b/README.md @@ -89,43 +89,44 @@ It's pretty self explanatory (click the "edit" icon to edit, etc.), but there ar The only *required* value is `data`. -| prop | type | default | description | -| ----------------------- | --------------------------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `data` | `object\|array` | | The data to be displayed / edited | -| `rootName` | `string` | `"data"` | A name to display in the editor as the root of the data object. | -| `onUpdate` | `UpdateFunction` | | A function to run whenever a value is **updated** (edit, delete *or* add) in the editor. See [Update functions](#update-functions). | -| `onEdit` | `UpdateFunction` | | A function to run whenever a value is **edited**. | -| `onDelete` | `UpdateFunction` | | A function to run whenever a value is **deleted**. | -| `onAdd` | `UpdateFunction` | | A function to run whenever a new property is **added**. | -| `onChange` | `OnChangeFunction` | | A function to modify/constrain user input as they type -- see [OnChange functions](#onchange-function). | -| `onError` | `OnErrorFunction` | | A function to run whenever the component reports an error -- see [OnErrorFunction](#onerror-function). | -| `showErrorMessages` | `boolean ` | `true` | Whether or not the component should display its own error messages (you'd probably only want to disable this if you provided your own `onError` function) | -| `enableClipboard` | `boolean\|CopyFunction` | `true` | Whether or not to enable the "Copy to clipboard" button in the UI. If a function is provided, `true` is assumed and this function will be run whenever an item is copied. | -| `indent` | `number` | `3` | Specify the amount of indentation for each level of nesting in the displayed data. | -| `collapse` | `boolean\|number\|FilterFunction` | `false` | Defines which nodes of the JSON tree will be displayed "opened" in the UI on load. If `boolean`, it'll be either all or none. A `number` specifies a nesting depth after which nodes will be closed. For more fine control a function can be provided — see [Filter functions](#filter-functions). | -| `restrictEdit` | `boolean\|FilterFunction` | `false` | If `false`, no editing is permitted. A function can be provided for more specificity — see [Filter functions](#filter-functions) | -| `restrictDelete` | `boolean\|FilterFunction` | `false` | As with `restrictEdit` but for deletion | -| `restrictAdd` | `boolean\|FilterFunction` | `false` | As with `restrictEdit` but for adding new properties | -| `restrictTypeSelection` | `boolean\|DataType[]\|TypeFilterFunction` | `false` | For restricting the data types the user can select. Can be a list of data types (e.g. `[ 'string', 'number', 'boolean', 'array', 'object', 'null' ]`) or a boolean. A function can be provided -- it should take the same input as the above `FilterFunction`s, but output should be `boolean \| DataType[]`. | -| `searchText` | `string` | `undefined` | Data visibility will be filtered by matching against value, using the method defined below in `searchFilter` | -| `searchFilter` | `"key"\|"value"\|"all"\|SearchFilterFunction` | `undefined` | Define how `searchText` should be matched to filter the visible items. See [Search/Filtering](#searchfiltering) | -| `searchDebounceTime` | `number` | `350` | Debounce time when `searchText` changes | -| `keySort` | `boolean\|CompareFunction` | `false` | If `true`, object keys will be ordered (using default JS `.sort()`). A [compare function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) can also be provided to define sorting behaviour. | -| `showArrayIndices` | `boolean` | `true` | Whether or not to display the index (as a property key) for array elements. | -| `showStringQuotes` | `boolean` | `true` | Whether or not to display string values in "quotes". | -| `showCollectionCount` | `boolean\|"when-closed"` | `true` | Whether or not to display the number of items in each collection (object or array). | -| `defaultValue` | `any\|DefaultValueFilterFunction` | `null` | When a new property is added, it is initialised with this value. A function can be provided with the same input as the `FilterFunction`s, but should output a value. This allows a different default value to be used depending on the data state (e.g. default for top level is an object, but a string elsewhere.) | -| `stringTruncate` | `number` | `250` | String values longer than this many characters will be displayed truncated (with `...`). The full string will always be visible when editing. | -| `translations` | `LocalisedStrings` object | `{ }` | UI strings (such as error messages) can be translated by passing an object containing localised string values (there are only a few). See [Localisation](#localisation) | -| `theme` | `string\|ThemeObject\|[string, ThemeObject]` | `"default"` | Either the name of one of the built-in themes, or an object specifying some or all theme properties. See [Themes](#themes--styles). | -| `className` | `string` | | Name of a CSS class to apply to the component. In most cases, specifying `theme` properties will be more straightforward. | -| `id` | `string` | | Name for the HTML `id` attribute on the main component container. | -| `icons` | `{[iconName]: JSX.Element, ... }` | `{ }` | Replace the built-in icons by specifying them here. See [Themes](#themes--styles). | | -| `minWidth` | `number\|string` (CSS value) | `250` | Minimum width for the editor container. | -| `maxWidth` | `number\|string` (CSS value) | `600` | Maximum width for the editor container. | -| `rootFontSize` | `number\|string` (CSS value) | `16px` | The "base" font size from which all other sizings are derived (in `em`s). By changing this you will scale the entire component. container. | -| `customNodeDefinitions` | `CustomNodeDefinition[]` | | You can provide customised components to override specific nodes in the data tree, according to a condition function. See see [Custom nodes](#custom-nodes) for more detail. (A simple custom component to turn url strings into active links is provided in the main package -- see [here](#active-hyperlinks)) | -| `customText` | `CustomTextDefinitions` | | In addition to [localising the component](#localisation) text strings, you can also *dynamically* alter it, depending on the data. See [Custom Text](#custom-text) for more detail. | +| prop | type | default | description | +| ----------------------- | --------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data` | `object\|array` | | The data to be displayed / edited | +| `rootName` | `string` | `"data"` | A name to display in the editor as the root of the data object. | +| `onUpdate` | `UpdateFunction` | | A function to run whenever a value is **updated** (edit, delete *or* add) in the editor. See [Update functions](#update-functions). | +| `onEdit` | `UpdateFunction` | | A function to run whenever a value is **edited**. | +| `onDelete` | `UpdateFunction` | | A function to run whenever a value is **deleted**. | +| `onAdd` | `UpdateFunction` | | A function to run whenever a new property is **added**. | +| `onChange` | `OnChangeFunction` | | A function to modify/constrain user input as they type -- see [OnChange functions](#onchange-function). | +| `onError` | `OnErrorFunction` | | A function to run whenever the component reports an error -- see [OnErrorFunction](#onerror-function). | +| `showErrorMessages` | `boolean ` | `true` | Whether or not the component should display its own error messages (you'd probably only want to disable this if you provided your own `onError` function) | +| `enableClipboard` | `boolean\|CopyFunction` | `true` | Whether or not to enable the "Copy to clipboard" button in the UI. If a function is provided, `true` is assumed and this function will be run whenever an item is copied. | +| `indent` | `number` | `3` | Specify the amount of indentation for each level of nesting in the displayed data. | +| `collapse` | `boolean\|number\|FilterFunction` | `false` | Defines which nodes of the JSON tree will be displayed "opened" in the UI on load. If `boolean`, it'll be either all or none. A `number` specifies a nesting depth after which nodes will be closed. For more fine control a function can be provided — see [Filter functions](#filter-functions). | +| `restrictEdit` | `boolean\|FilterFunction` | `false` | If `false`, no editing is permitted. A function can be provided for more specificity — see [Filter functions](#filter-functions) | +| `restrictDelete` | `boolean\|FilterFunction` | `false` | As with `restrictEdit` but for deletion | +| `restrictAdd` | `boolean\|FilterFunction` | `false` | As with `restrictEdit` but for adding new properties | +| `restrictTypeSelection` | `boolean\|DataType[]\|TypeFilterFunction` | `false` | For restricting the data types the user can select. Can be a list of data types (e.g. `[ 'string', 'number', 'boolean', 'array', 'object', 'null' ]`) or a boolean. A function can be provided -- it should take the same input as the above `FilterFunction`s, but output should be `boolean \| DataType[]`. | +| `searchText` | `string` | `undefined` | Data visibility will be filtered by matching against value, using the method defined below in `searchFilter` | +| `searchFilter` | `"key"\|"value"\|"all"\|SearchFilterFunction` | `undefined` | Define how `searchText` should be matched to filter the visible items. See [Search/Filtering](#searchfiltering) | +| `searchDebounceTime` | `number` | `350` | Debounce time when `searchText` changes | +| `keySort` | `boolean\|CompareFunction` | `false` | If `true`, object keys will be ordered (using default JS `.sort()`). A [compare function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) can also be provided to define sorting behaviour. | +| `showArrayIndices` | `boolean` | `true` | Whether or not to display the index (as a property key) for array elements. | +| `showStringQuotes` | `boolean` | `true` | Whether or not to display string values in "quotes". | +| `showCollectionCount` | `boolean\|"when-closed"` | `true` | Whether or not to display the number of items in each collection (object or array). | +| `defaultValue` | `any\|DefaultValueFilterFunction` | `null` | When a new property is added, it is initialised with this value. A function can be provided with the same input as the `FilterFunction`s, but should output a value. This allows a different default value to be used depending on the data state (e.g. default for top level is an object, but a string elsewhere.) | +| `stringTruncate` | `number` | `250` | String values longer than this many characters will be displayed truncated (with `...`). The full string will always be visible when editing. | +| `translations` | `LocalisedStrings` object | `{ }` | UI strings (such as error messages) can be translated by passing an object containing localised string values (there are only a few). See [Localisation](#localisation) | +| `theme` | `string\|ThemeObject\|[string, ThemeObject]` | `"default"` | Either the name of one of the built-in themes, or an object specifying some or all theme properties. See [Themes](#themes--styles). | +| `className` | `string` | | Name of a CSS class to apply to the component. In most cases, specifying `theme` properties will be more straightforward. | +| `id` | `string` | | Name for the HTML `id` attribute on the main component container. | +| `icons` | `{[iconName]: JSX.Element, ... }` | `{ }` | Replace the built-in icons by specifying them here. See [Themes](#themes--styles). | | +| `minWidth` | `number\|string` (CSS value) | `250` | Minimum width for the editor container. | +| `maxWidth` | `number\|string` (CSS value) | `600` | Maximum width for the editor container. | +| `rootFontSize` | `number\|string` (CSS value) | `16px` | The "base" font size from which all other sizings are derived (in `em`s). By changing this you will scale the entire component. container. | +| `customNodeDefinitions` | `CustomNodeDefinition[]` | | You can provide customised components to override specific nodes in the data tree, according to a condition function. See see [Custom nodes](#custom-nodes) for more detail. (A simple custom component to turn url strings into active links is provided in the main package -- see [here](#active-hyperlinks)) | +| `customText` | `CustomTextDefinitions` | | In addition to [localising the component](#localisation) text strings, you can also *dynamically* alter it, depending on the data. See [Custom Text](#custom-text) for more detail. | +| `useJSON5Editor` | `boolean` \| `JSON5StringifyOptions` | | When editing whole objects as JSON text, you can always enter text in the more convenient [JSON5](https://json5.org) format. However, by setting `useJSON5Editor: true`, the text input will be *displayed* as JSON5 when the input area appears. (You can also provide a JSON5.stringify `options` object to specify the exact formatting) | ## Update functions @@ -611,6 +612,7 @@ This component is heavily inspired by [react-json-view](https://github.com/mac-s - **1.12.0**: - Preserve editing mode when changing Data Type - [`onError` callback](#onerror-function) available for custom error handling + - option to display JSON text as [JSON5](https://json5.org/) - **1.11.8**: Fix regression for empty data root name introduces in 1.11.7 - **1.11.7**: Handle \ object keys / prevent duplicate keys - **1.11.3**: Bug fix for invalid state when changing type to Collection node diff --git a/demo/src/App.tsx b/demo/src/App.tsx index 18ebe0d8..688102a7 100644 --- a/demo/src/App.tsx +++ b/demo/src/App.tsx @@ -309,6 +309,7 @@ function App() { customNodeDefinitions={demoData[selectedData]?.customNodeDefinitions} customText={demoData[selectedData]?.customTextDefinitions} onChange={demoData[selectedData]?.onChange ?? undefined} + useJSON5Editor /> diff --git a/src/CollectionNode.tsx b/src/CollectionNode.tsx index 78f0d4ec..54b252fa 100644 --- a/src/CollectionNode.tsx +++ b/src/CollectionNode.tsx @@ -9,7 +9,7 @@ import { type NodeData, type JerError, ERROR_DISPLAY_TIME, - CollectionData, + type CollectionData, } from './types' import { Icon } from './Icons' import { filterNode, isCollection } from './filterHelpers' @@ -55,8 +55,17 @@ export const CollectionNode: React.FC = ({ defaultValue, translate, customNodeDefinitions, + useJSON5Editor, } = props - const [stringifiedValue, setStringifiedValue] = useState(JSON.stringify(data, null, 2)) + const stringifyJson = useMemo(() => { + if (!useJSON5Editor) return (data: object) => JSON.stringify(data, null, 2) + if (useJSON5Editor instanceof Object) { + return (data: object) => JSON5.stringify(data, useJSON5Editor) + } + return (data: object) => JSON5.stringify(data, { space: 2 }) + }, [useJSON5Editor, indent]) + + const [stringifiedValue, setStringifiedValue] = useState(stringifyJson(data)) const [error, setError] = useState(null) const startCollapsed = collapseFilter(incomingNodeData) @@ -77,7 +86,7 @@ export const CollectionNode: React.FC = ({ const [isAnimating, setIsAnimating] = useState(false) useEffect(() => { - setStringifiedValue(JSON.stringify(data, null, 2)) + setStringifiedValue(stringifyJson(data)) }, [data]) useEffect(() => { @@ -249,7 +258,7 @@ export const CollectionNode: React.FC = ({ const handleCancel = () => { setCurrentlyEditingElement(null) setError(null) - setStringifiedValue(JSON.stringify(data, null, 2)) + setStringifiedValue(stringifyJson(data)) } // DERIVED VALUES (this makes the JSX logic less messy) diff --git a/src/JsonEditor.tsx b/src/JsonEditor.tsx index 6dcbde59..2321cf9d 100644 --- a/src/JsonEditor.tsx +++ b/src/JsonEditor.tsx @@ -55,6 +55,7 @@ const Editor: React.FC = ({ id, customText = {}, customNodeDefinitions = [], + useJSON5Editor = false, }) => { const { getStyles, setTheme, setIcons } = useTheme() const { setCollapseState } = useTreeState() @@ -196,6 +197,7 @@ const Editor: React.FC = ({ translate, customNodeDefinitions, parentData: null, + useJSON5Editor, } const mainContainerStyles = { ...getStyles('container', nodeData), minWidth, maxWidth } diff --git a/src/types.ts b/src/types.ts index 5f0b40ee..52b3d4b6 100644 --- a/src/types.ts +++ b/src/types.ts @@ -40,6 +40,7 @@ export interface JsonEditorProps { translations?: Partial customNodeDefinitions?: CustomNodeDefinition[] customText?: CustomTextDefinitions + useJSON5Editor?: boolean | JSON5StringifyOptions } const ValueDataTypes = ['string', 'number', 'boolean', 'null'] as const @@ -175,6 +176,7 @@ export interface CollectionNodeProps extends BaseNodeProps { showCollectionCount: boolean | 'when-closed' showStringQuotes: boolean defaultValue: unknown + useJSON5Editor: boolean | JSON5StringifyOptions } export type ValueData = string | number | boolean @@ -305,3 +307,14 @@ export type ThemeInput = | Theme | Partial | Array> + +// JSON5 options +export interface JSON5StringifyOptions { + space?: number + quote?: string + replacer?: + | Array + | ((this: any, key: string, value: any) => any) + | null + | undefined +} From 0f4e6dd165a0852e0652b705d5fe12c2f6283c91 Mon Sep 17 00:00:00 2001 From: Carl Smith <5456533+CarlosNZ@users.noreply.github.com> Date: Mon, 17 Jun 2024 21:54:16 +1200 Subject: [PATCH 7/8] Update version --- package.json | 2 +- rollup.config.mjs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 636c94e2..f964027d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "json-edit-react", - "version": "1.12.0-rc1", + "version": "1.12.0-rc2", "description": "React component for editing or viewing JSON/object data", "main": "build/index.cjs.js", "module": "build/index.esm.js", diff --git a/rollup.config.mjs b/rollup.config.mjs index 283faaf3..48514f25 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -29,7 +29,7 @@ export default [ bundleSize(), sizes(), ], - external: [], + external: ['json5', 'just-clone', 'object-property-assigner', 'object-property-extractor'], }, { input: './build/dts/index.d.ts', From 6238968db978e765caeb51d9f96beb0eea2230cd Mon Sep 17 00:00:00 2001 From: Carl Smith <5456533+CarlosNZ@users.noreply.github.com> Date: Mon, 17 Jun 2024 22:15:27 +1200 Subject: [PATCH 8/8] Update useMemo dependency --- src/CollectionNode.tsx | 2 +- src/types.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CollectionNode.tsx b/src/CollectionNode.tsx index 3f27a3eb..e8bc60a1 100644 --- a/src/CollectionNode.tsx +++ b/src/CollectionNode.tsx @@ -63,7 +63,7 @@ export const CollectionNode: React.FC = ({ return (data: object) => JSON5.stringify(data, useJSON5Editor) } return (data: object) => JSON5.stringify(data, { space: 2 }) - }, [useJSON5Editor, indent]) + }, [useJSON5Editor]) const [stringifiedValue, setStringifiedValue] = useState(stringifyJson(data)) const [error, setError] = useState(null) diff --git a/src/types.ts b/src/types.ts index 52b3d4b6..7c1a277f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -308,7 +308,7 @@ export type ThemeInput = | Partial | Array> -// JSON5 options +// JSON5 options (https://json5.org/) export interface JSON5StringifyOptions { space?: number quote?: string