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/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', diff --git a/src/CollectionNode.tsx b/src/CollectionNode.tsx index 78f0d4ec..e8bc60a1 100644 --- a/src/CollectionNode.tsx +++ b/src/CollectionNode.tsx @@ -8,8 +8,8 @@ import { type ErrorString, type NodeData, type JerError, + type CollectionData, ERROR_DISPLAY_TIME, - 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]) + + 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..7c1a277f 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 (https://json5.org/) +export interface JSON5StringifyOptions { + space?: number + quote?: string + replacer?: + | Array + | ((this: any, key: string, value: any) => any) + | null + | undefined +}