json-edit-react 1.25.0-beta1 → 1.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,11 +1,14 @@
1
- <!-- This README was converted from GitHub-flavored Markdown to npm format. -->
2
- <!-- Some elements may display differently on npmjs.com vs. GitHub. -->
1
+ <!-- The README that will appear on the package's NPM page: https://www.npmjs.com/package/json-edit-react
2
+ On publish, this file is temporarily renamed to the main README so it gets published to npm,
3
+ then renamed back to this file, so the primary README remains published to Github repo.
3
4
 
4
- # json-edit-react
5
+ The {{BLOCKS}} below are replaced from the equivalent blocks in the main README file when the
6
+ `yarn prepareReadme` script is run (which also happens before publish).
7
+ -->
5
8
 
6
9
  <img width="60" alt="screenshot" src="image/logo192.png" style="float:left; margin-right: 1em;">
7
10
 
8
- A [React](https://github.com/facebook/react) component for editing or viewing JSON/object data
11
+ A highly-configurable [React](https://github.com/facebook/react) component for editing or viewing JSON/object data
9
12
 
10
13
 
11
14
  ## [Explore the Demo](https://carlosnz.github.io/json-edit-react/) <!-- omit in toc -->
@@ -19,74 +22,30 @@ A [React](https://github.com/facebook/react) component for editing or viewing JS
19
22
  - ✅ **Easy inline editing** of individual values or whole blocks of JSON text
20
23
  - 🔒 **Granular control** – restrict edits, deletions, or additions per element
21
24
  - 📏 **[JSON Schema](https://json-schema.org/) validation** (using 3rd-party validation library)
22
- - 🎨 **Customisable UI** — built-in or custom [themes](#themes--styles), CSS overrides or targeted classes
25
+ - 🎨 **Customisable UI** — built-in or custom [themes](https://github.com/CarlosNZ/json-edit-react#themes--styles), CSS overrides or targeted classes
23
26
  - 📦 **Self-contained** — plain HTML/CSS, so no dependence on external UI libraries
24
27
  - 🔍 **Search & filter** — find data by key, value or custom function
25
- - 🚧 **[Custom components](#custom-nodes)** — replace specific nodes with specialised components (e.g. date picker, links, images)
26
- - 🌏 **[Localisation](#localisation)** — easily translate UI labels and messages
27
- - 🔄 **[Drag-n-drop](#drag-n-drop)** re-ordering within objects/arrays
28
- - 🎹 **[Keyboard customisation](#keyboard-customisation)** — define your own key bindings
29
- - 🎮 **[External control](#external-control-1)** via callbacks and triggers
28
+ - 🚧 **[Custom components](https://github.com/CarlosNZ/json-edit-react#custom-nodes)** — replace specific nodes with specialised components (e.g. date picker, links, images)
29
+ - 🌏 **[Localisation](https://github.com/CarlosNZ/json-edit-react#localisation)** — easily translate UI labels and messages
30
+ - 🔄 **[Drag-n-drop](https://github.com/CarlosNZ/json-edit-react#drag-n-drop)** re-ordering within objects/arrays
31
+ - 🎹 **[Keyboard customisation](https://github.com/CarlosNZ/json-edit-react#keyboard-customisation)** — define your own key bindings
32
+ - 🎮 **[External control](https://github.com/CarlosNZ/json-edit-react#external-control-1)** via callbacks and triggers
30
33
 
31
34
  💡 Try the **[Live Demo](https://carlosnz.github.io/json-edit-react/)** to see these features in action!
32
35
 
33
36
  <img width="392" alt="screenshot" src="image/screenshot.png">
34
37
 
35
- <div style="padding: 8px; border-left: 4px solid #888; background-color: #f8f8f8; margin: 10px 0;">
36
- ❗ **Important**
38
+
39
+ <div style="background-color: #f6f8fa; border-left: 4px solid #d63384; padding: 15px; margin: 15px 0; border-radius: 3px;">
40
+ <p style="margin: 0 0 10px 0; color: #d63384;">
41
+ <strong>🚨 IMPORTANT:</strong>
42
+ </p>
37
43
 
38
44
  Breaking changes:
39
- - **Version 1.19.0** has a change to the `theme` input. Built-in themes must now be imported separately and passed in, rather than just naming the theme as a string. This is better for tree-shaking, so unused themes won't be bundled with your build. See [Themes & Styles](#themes--styles).
40
- - **Version 1.14.0** has a change which recommends you provide a `setData` prop and not use `onUpdate` for updating your data externally. See [Managing state](#managing-state).
45
+ - **Version 1.19.0** has a change to the `theme` input. Built-in themes must now be imported separately and passed in, rather than just naming the theme as a string. This is better for tree-shaking, so unused themes won't be bundled with your build. See [Themes & Styles](https://github.com/CarlosNZ/json-edit-react#themes--styles).
46
+ - **Version 1.14.0** has a change which recommends you provide a `setData` prop and not use `onUpdate` for updating your data externally. See [Managing state](https://github.com/CarlosNZ/json-edit-react#managing-state).
41
47
  </div>
42
48
 
43
- ## Contents <!-- omit in toc -->
44
- - [Installation](#installation)
45
- - [Implementation](#implementation)
46
- - [Usage](#usage)
47
- - [Props overview](#props-overview)
48
- - [Data management](#data-management)
49
- - [Restricting editing](#restricting-editing)
50
- - [Look and Feel / UI](#look-and-feel--ui)
51
- - [Search and Filtering](#search-and-filtering)
52
- - [Custom components \& overrides (incl. Localisation)](#custom-components--overrides-incl-localisation)
53
- - [External control](#external-control)
54
- - [Miscellaneous](#miscellaneous)
55
- - [Managing state](#managing-state)
56
- - [Update functions](#update-functions)
57
- - [OnChange function](#onchange-function)
58
- - [OnError function](#onerror-function)
59
- - [Copy function](#copy-function)
60
- - [Filter functions](#filter-functions)
61
- - [TypeFilterFunction](#typefilterfunction)
62
- - [Examples](#examples-1)
63
- - [JSON Schema validation](#json-schema-validation)
64
- - [Drag-n-drop](#drag-n-drop)
65
- - [Full object editing](#full-object-editing)
66
- - [Search/Filtering](#searchfiltering)
67
- - [Themes \& Styles](#themes--styles)
68
- - [Fragments](#fragments)
69
- - [A note about sizing and scaling](#a-note-about-sizing-and-scaling)
70
- - [Icons](#icons)
71
- - [Localisation](#localisation)
72
- - [Custom Nodes](#custom-nodes)
73
- - [Active hyperlinks](#active-hyperlinks)
74
- - [Custom Collection nodes](#custom-collection-nodes)
75
- - [Custom Text](#custom-text)
76
- - [Custom Buttons](#custom-buttons)
77
- - [Keyboard customisation](#keyboard-customisation)
78
- - [External control](#external-control-1)
79
- - [Event callbacks](#event-callbacks)
80
- - [Event triggers](#event-triggers)
81
- - [Undo functionality](#undo-functionality)
82
- - [Exported helpers](#exported-helpers)
83
- - [Functions \& Components](#functions--components)
84
- - [Types](#types)
85
- - [Issues, bugs, suggestions?](#issues-bugs-suggestions)
86
- - [Roadmap](#roadmap)
87
- - [Inspiration](#inspiration)
88
- - [Changelog](#changelog)
89
-
90
49
  ## Installation
91
50
 
92
51
  ```sh
@@ -117,925 +76,21 @@ return (
117
76
 
118
77
  It's pretty self explanatory (click the "edit" icon to edit, etc.), but there are a few not-so-obvious ways of interacting with the editor:
119
78
 
120
- - Double-click a value (or a key) to edit it
79
+ - **Double-click** a value (or a key) to edit it
121
80
  - When editing a string, use `Cmd/Ctrl/Shift-Enter` to add a new line (`Enter` submits the value)
122
- - It's the opposite when editing a full object/array node (which you do by clicking "edit" on an object or array value) — `Enter` for new line, and `Cmd/Ctrl/Shift-Enter` for submit
123
- - `Escape` to cancel editing
124
- - When clicking the "clipboard" icon, holding down `Cmd/Ctrl` will copy the *path* to the selected node rather than its value
81
+ - It's the opposite when editing a full object/array node (which you do by **clicking "edit"** on an object or array value) — `Enter` for new line, and `Cmd/Ctrl/Shift-Enter` for submit
82
+ - `Escape` to **cancel** editing
83
+ - When clicking the "**clipboard**" icon, holding down `Cmd/Ctrl` will copy the *path* to the selected node rather than its value
125
84
  - When opening/closing a node, hold down "Alt/Option" to open/close *all* child nodes at once
126
- - For Number inputs, arrow-up and down keys will increment/decrement the value
127
- - For Boolean inputs, space bar will toggle the value
128
- - Easily move to the next/previous node (for editing) using the `Tab`/`Shift-Tab` key
129
- - Drag and drop items to change the structure or modify display order
85
+ - For Number inputs, **arrow-up** and **down** keys will increment/decrement the value
86
+ - For Boolean inputs, **space bar** will toggle the value
87
+ - Easily navigate to the next or previous node for editing using the `Tab`/`Shift-Tab` keys.
88
+ - **Drag and drop** items to change the structure or modify display order
130
89
  - When editing is not permitted, double-clicking a string value will expand the text to the full value if it is truncated due to length (there is also a clickable "..." for long strings)
131
- - JSON text input can accept "looser" input, if an additional JSON parsing method is provided (e.g. [JSON5](https://json5.org/)). See `jsonParse` prop.
90
+ - **JSON text input** can accept "looser" input, if an additional JSON parsing method is provided (e.g. [JSON5](https://json5.org/)). See `jsonParse` prop.
132
91
 
133
92
  [Have a play with the Demo app](https://carlosnz.github.io/json-edit-react/) to get a feel for it!
134
93
 
135
- ## Props overview
136
-
137
- The only *required* property is `data` (although you will need to provide a `setData` method to update your data).
138
-
139
- ### Data management
140
-
141
- | Prop | Type | Default | Description |
142
- | ----------------- | ----------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
143
- | `data` | `object\|array` | none | The data to be displayed / edited |
144
- | `setData` | `object\|array => void` | none | Method to update your `data` object. See [Managing state](#managing-state) below for additional notes. |
145
- | `onUpdate` | `UpdateFunction` | none | A function to run whenever a value is **updated** (edit, delete *or* add) in the editor. See [Update functions](#update-functions). |
146
- | `onEdit` | `UpdateFunction` | none | A function to run whenever a value is **edited**. |
147
- | `onDelete` | `UpdateFunction` | none | A function to run whenever a value is **deleted**. |
148
- | `onAdd` | `UpdateFunction` | none | A function to run whenever a new property is **added**. |
149
- | `onChange` | `OnChangeFunction` | none | A function to modify/constrain user input as they type — see [OnChange functions](#onchange-function). |
150
- | `onError` | `OnErrorFunction` | none | A function to run whenever the component reports an error — see [OnErrorFunction](#onerror-function). |
151
- | `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 — see [Copy Function](#copy-function) |
152
-
153
-
154
- ### Restricting editing
155
-
156
- | Prop | Type | Default | Description |
157
- | ----------------------- | ----------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
158
- | `restrictEdit` | `boolean\|FilterFunction` | `false` | If `true`, no editing at all is permitted. A function can be provided for more specificity — see [Filter functions](#filter-functions) |
159
- | `restrictDelete` | `boolean\|FilterFunction` | `false` | As with `restrictEdit` but for deletion |
160
- | `restrictAdd` | `boolean\|FilterFunction` | `false` | As with `restrictEdit` but for adding new properties |
161
- | `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' ]`), a boolean. or a function — see [TypeFilterFunction](#typefilterfunction) |
162
- | `restrictDrag` | `boolean\|FilterFunction` | `true` | Set to `false` to enable drag and drop functionality — see [Drag-n-drop](#drag-n-drop) |
163
- | `viewOnly` | `boolean` | | A shorthand if you just want the component to be a viewer, with no editing. Overrides any values of the above edit restrictions. |
164
-
165
- ### Look and Feel / UI
166
-
167
- | Prop | Type | Default | Description |
168
- | ----------------------- | ----------------------------------------- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
169
- | `theme` | `ThemeInput` | `default` | Either one of the built-in themes (imported separately), or an object specifying some or all theme properties — see [Themes](#themes--styles). |
170
- | `icons` | `{[iconName]: JSX.Element, ... }` | `{ }` | Replace the built-in icons by specifying them here — see [Themes](#themes--styles). | |
171
- | `indent` | `number` | `3` | Specify the amount of indentation for each level of nesting in the displayed data. |
172
- | `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). |
173
- | `collapseAnimationTime` | `number` | `300` | Time (in milliseconds) for the transition animation when collapsing collection nodes. |
174
- | `collapseClickZones` | `Array<"left" \| "header" \| "property">` | `["left", "header"]` | Aside from the <span style="font-size: 140%">`⌄`</span> icon, you can specify other regions of the UI to be clickable for collapsing/opening a collection. |
175
- | `rootName` | `string` | `"data"` | A name to display in the editor as the root of the data object. |
176
- | `showArrayIndices` | `boolean` | `true` | Whether or not to display the index (as a property key) for array elements. |
177
- | `showStringQuotes` | `boolean` | `true` | Whether or not to display string values in "quotes". |
178
- | `showCollectionCount` | `boolean\|"when-closed"` | `true` | Whether or not to display the number of items in each collection (object or array). |
179
- | `stringTruncate` | `number` | `250` | String values longer than this many characters will be displayed truncated (with `...`). The full string will always be visible when editing. |
180
- | `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, except the input type should be a tuple of the key and the value of a node i.e. `(a: [string \| number, ValueData], b: [string \| number, ValueData]) => number` |
181
- | `minWidth` | `number\|string` (CSS value) | `250` | Minimum width for the editor container. |
182
- | `maxWidth` | `number\|string` (CSS value) | `600` | Maximum width for the editor container. |
183
- | `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. |
184
- | `insertAtTop` | `boolean\| "object \| "array"` | `false` | If `true`, inserts new values at the *top* rather than bottom. Can set the behaviour just for arrays or objects by setting to `"object"` or `"array"` respectively. | |
185
- | `errorMessageTimeout` | `number` | `2500` | Time (in milliseconds) to display the error message in the UI. | |
186
- | `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) |
187
-
188
-
189
- ### Search and Filtering
190
-
191
- | Prop | Type | Default | Description |
192
- | -------------------- | --------------------------------------------- | ----------- | --------------------------------------------------------------------------------------------------------------- |
193
- | `searchText` | `string` | `undefined` | Data visibility will be filtered by matching against value, using the method defined below in `searchFilter` |
194
- | `searchFilter` | `"key"\|"value"\|"all"\|SearchFilterFunction` | `undefined` | Define how `searchText` should be matched to filter the visible items. See [Search/Filtering](#searchfiltering) |
195
- | `searchDebounceTime` | `number` | `350` | Debounce time when `searchText` changes |
196
-
197
-
198
- ### Custom components & overrides (incl. Localisation)
199
-
200
- | Prop | Type | Default | Description |
201
- | ----------------------- | --------------------------------- | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
202
- | `customNodeDefinitions` | `CustomNodeDefinition[]` | | You can provide custom React components to override specific nodes in the data tree, according to a condition function — see [Custom nodes](#custom-nodes). (A simple custom component to turn url strings into active links is provided in the main package — see [here](#active-hyperlinks)) |
203
- | `customButtons` | `CustomButtonDefinition[]` | `[]` | You can add your own buttons to the Edit Buttons panel if you'd like to be able to perform a custom operation on the data. See [Custom Buttons](#custom-buttons) |
204
- | `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) |
205
- | `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. |
206
- | `TextEditor` | `ReactComponent<TextEditorProps>` | | Pass a component to offer a custom text/code editor when editing full JSON object as text. [See details](#full-object-editing) |
207
- | `jsonParse` | `(input: string) => JsonData` | `JSON.parse` | When editing a block of JSON directly, you may wish to allow some "looser" input -- e.g. 'single quotes', trailing commas, or unquoted field names. In this case, you can provide a third-party JSON parsing method. I recommend [JSON5](https://json5.org/), which is what is used in the [Demo](https://carlosnz.github.io/json-edit-react/) |
208
- | `jsonStringify` | `(data: JsonData) => string` | `(data) => JSON.stringify(data, null, 2)` | Similarly, you can override the default presentation of the JSON string when starting editing JSON. You can supply different formatting parameters to the native `JSON.stringify()`, or provide a third-party option, like the aforementioned JSON5. |
209
- | `keyboardControls` | `KeyboardControls` | As explained [above](#usage) | Override some or all of the keyboard controls. See [Keyboard customisation](#keyboard-customisation) for details. |
210
- ### External control
211
-
212
- More detail [below](#external-control-1)
213
-
214
- | Prop | Type | Default | Description |
215
- | ------------------ | --------------------- | ------- | -------------------------------------------------------------------- |
216
- | `onEditEvent` | `OnEditEventFunction` | none | Callback to execute whenever the user starts or stops editing a node |
217
- | `onCollapse` | `OnCollapseFunction` | none | Callback to execute whenever the user collapses or opens a node |
218
- | `externalTriggers` | `ExternalTriggers` | none | Specify a node to collapse/open, or to start/stop editing | |
219
-
220
- ### Miscellaneous
221
-
222
- | Prop | Type | Default | Description |
223
- | -------------- | --------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
224
- | `defaultValue` | `any\|DefaultValueFilterFunction` | `null` | When a new property is added, it is initialised with this value. A [function can be provided](#filter-functions) with the almost 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.) |
225
- | `id` | `string` | none | Name for the HTML `id` attribute on the main component container. |
226
- | `className` | `string` | none | Name of a CSS class to apply to the overall component. In most cases, specifying `theme` properties will be more straightforward. |
227
-
228
-
229
- ----
230
-
231
- ## Managing state
232
-
233
- It is recommended that you manage the `data` state yourself outside this component — just pass in a `setData` method, which is called internally to update your `data`. However, this is not compulsory -- if you don't provide a `setData` method, the data will be managed internally, which would be fine if you're not doing anything with the data. The alternative is to use the [Update functions](#update-functions) to update your `data` externally, but this is not recommended except in special circumstances as you can run into issues keeping your data in sync with the internal state (which is what is displayed), as well as unnecessary re-renders. Update functions should ideally be used only for implementing side effects (e.g. notifications), validation, or mutating the data before setting it with `setData`.
234
-
235
- ## Update functions
236
-
237
- A callback to be executed whenever a data update (edit, delete or add) occurs can be provided. You might wish to use this to update some external state, make an API call, modify the data before saving it, or [validate the data structure](#json-schema-validation) against a JSON schema. If you want the same function for all updates, then just the `onUpdate` prop is sufficient. However, should you require something different for editing, deletion and addition, then you can provide separate Update functions via the `onEdit`, `onDelete` and `onAdd` props.
238
-
239
- The function will receive the following object as a parameter:
240
-
241
- ```js
242
- {
243
- newData, // data state after update
244
- currentData, // data state before update
245
- newValue, // the new value of the property being updated
246
- currentValue, // the current value of the property being updated
247
- name, // name of the property being updated
248
- path // full path to the property being updated, as an array of property keys
249
- // (e.g. [ "user", "friends", 1, "name" ] ) (equivalent to "user.friends[1].name")
250
- }
251
- ```
252
- The function can return nothing (in which case the data is updated normally), or a value to represent success/failure, error value, or modified data. The return value can be one of the following, and handled accordingly:
253
- - `true` / `void` / `undefined`: data continues update as normal
254
- - `false`: considers the update to be an error, so data is not updated (reverts to previous value), and a generic error message is displayed in the UI
255
- - `string`: also considered an error, so no data update, but the UI error message will be your provided string
256
- - `[ "value", <value> ]`: tells the component to use the returned `<value>` instead of the input data. You might use this to automatically modify user input -- for example, sorting an array, or inserting a timestamp field into an object.
257
- - `[ "error", <value> ]`: same as `string`, but in the longer tuple format.
258
-
259
- ### OnChange function
260
-
261
- Similar to the Update functions, the `onChange` function is executed as the user input changes. You can use this to restrict or constrain user input -- e.g. limiting numbers to positive values, or preventing line breaks in strings. The function *must* return a value in order to update the user input field, so if no changes are to made, just return it unmodified.
262
-
263
- The input object is similar to the Update function input, but with no `newData` field (since this operation occurs before the data is updated).
264
-
265
- #### Examples
266
-
267
- - Restrict "age" inputs to positive values up to 100:
268
- ```js
269
- // in <JsonEditor /> props
270
- onChange = ({ newValue, name }) => {
271
- if (name === "age" && newValue < 0) return 0;
272
- if (name === "age" && newValue > 100) return 100;
273
- return newValue
274
- }
275
- ```
276
- - Only allow alphabetical or whitespace input for "name" field (including no line breaks):
277
- ```js
278
- onChange = ({ newValue, name }) => {
279
- if (name === 'name' && typeof newValue === "string")
280
- return newValue.replace(/[^a-zA-Z\s]|\n|\r/gm, '');
281
- return newValue;
282
- }
283
- ```
284
-
285
- ### OnError function
286
-
287
- 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:
288
-
289
- ```js
290
- {
291
- currentData, // data state before update
292
- currentValue, // the current value of the property being updated
293
- errorValue, // the erroneous value that failed to update the property
294
- name, // name of the property being updated
295
- path, // full path to the property being updated, as an array of property keys
296
- // (e.g. [ "user", "friends", 1, "name" ] ) (equivalent to "user.friends[1].name"),
297
- error: {
298
- code, // one of 'UPDATE_ERROR' | 'DELETE_ERROR' | 'ADD_ERROR' | 'INVALID_JSON' | 'KEY_EXISTS'
299
- message // the (localised) error message that would be displayed
300
- }
301
- }
302
- ```
303
- (An example of a custom Error UI can be seen in the [Demo](#https://carlosnz.github.io/json-edit-react/?data=customNodes) with the "Custom Nodes" data set -- when you enter invalid JSON input a "Toast" notification is displayed instead of the normal component error message.)
304
-
305
- ### Copy function
306
-
307
- 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:
308
-
309
- ```js
310
- key // name of the property being copied
311
- path // path to the property
312
- value // the value copied to the clipboard
313
- type // Either "path" or "value" depending on whether "Cmd/Ctrl" was pressed
314
- stringValue // A nicely stringified version of `value`
315
- // (i.e. what the clipboard actually receives)
316
- success // true/false -- whether clipboard copy action actually succeeded
317
- errorMessage // Error detail if `success === false`
318
- ```
319
-
320
- Since there is very little user feedback when clicking "Copy", a good idea would be to present some kind of notification in this callback (see [Demo](https://carlosnz.github.io/json-edit-react/)). There are situations (such as an insecure environment) where the browser won't actually permit any clipboard actions. In this case, the `success` property will be `false`, so you can handle it appropriately.
321
-
322
-
323
- ## Filter functions
324
-
325
- You can control which nodes of the data structure can be edited, deleted, or added to, or have their data type changed, by passing Filter functions. These will be called on each property in the data and the attribute will be enforced depending on whether the function returns `true` or `false` (`true` means *cannot* be edited).
326
-
327
- The function receives the following object:
328
- ```js
329
- {
330
- key, // name of the property
331
- path, // path to the property (as an array of property keys)
332
- level, // depth of the property (with 0 being the root)
333
- index, // index of the node within its collection (based on display order)
334
- value, // value of the property
335
- size , // if a collection (object, array), the number of items (null for non-collections)
336
- parentData, // parent object containing the current node
337
- fullData // the full (overall) data object
338
- collapsed // whether or not the current node is in a
339
- // "collapsed" state (only for Collection nodes)
340
- }
341
- ```
342
-
343
- A Filter function is available for the `collapse` prop as well, so you can have your data appear with deeply-nested collections opened up, while collapsing everything else, for example.
344
-
345
- ### TypeFilterFunction
346
-
347
- For restricting data types, the (Type) filter function is slightly more sophisticated. The input is the same, but the output can be either a `boolean` (which would restrict the available types for a given node to either *all* or *none*), or an array of data types to be restricted to. The available values are:
348
- - `"string"`
349
- - `"number"`
350
- - `"boolean"`
351
- - `"null"`
352
- - `"object"`
353
- - `"array"`
354
-
355
- There is no specific restriction function for editing object key names, but they must return `false` for *both* `restrictEdit` and `restrictDelete` (and `restrictAdd` for collections), since changing a key name is equivalent to deleting a property and adding a new one.
356
-
357
- You can also set a dynamic default value by passing a filter function to the `defaultValue` prop -- the input is the same as the above, but also takes the new `key` value as its second parameter, so the new value can depend on the new key added.
358
-
359
- Using all these restriction filters together can allow you to enforce a reasonably sophisticated data schema.
360
-
361
- ### Examples
362
-
363
- - A good case would be ensure your root node is not directly editable:
364
-
365
- ```js
366
- // in <JsonEditor /> props
367
- restrictEdit = { ({ level }) => level === 0 }
368
- ```
369
-
370
- - Don't let the `id` field be edited:
371
-
372
- ```js
373
- restrictEdit = { ({ key }) => key === "id" }
374
- // You'd probably want to include this in `restrictDelete` as well
375
- ```
376
-
377
- - Only individual properties can be deleted, not objects or arrays:
378
-
379
- ```js
380
- restrictDelete = { ({ size }) => size !== null }
381
- ```
382
-
383
- - The only collections that can have new items added are the "address" object and the "users" array:
384
- ```js
385
- restrictAdd = { ({ key }) => key !== "address" && key !== "users" }
386
- // "Adding" is irrelevant for non-collection nodes
387
- ```
388
-
389
- - Multiple type restrictions:
390
- - `string` values can only be changed to strings or objects (for nesting)
391
- - `null` is not allowed anywhere
392
- - `boolean` values must remain boolean
393
- - data nested below the "user" field can be any simple property (i.e. not objects or arrays), and doesn't have to follow the above rules (except no "null")
394
- ```js
395
- restrictTypeSelection = { ({ path, value }) => {
396
- if (path.includes('user')) return ['string', 'number', 'boolean']
397
- if (typeof value === 'boolean') return false
398
- if (typeof value === 'string') return ['string', 'object']
399
- return ['string', 'number', 'boolean', 'array', 'object'] // no "null"
400
- } }
401
- ```
402
-
403
- ### JSON Schema validation
404
-
405
- As well as dynamically controlling *access* to the various edit tools as described above, it's possible to do full [JSON Schema](https://json-schema.org/) validation by creating an [Update Function](#update-functions) that passes the data to a 3rd-party schema validation library (e.g. [Ajv](https://ajv.js.org/)). This will then reject any invalid input, and display an error in the UI (or via a custom [onError](#onerror-function) function). You can see an example of this in the [Demo](https://carlosnz.github.io/json-edit-react/?data=jsonSchemaValidation) with the "JSON Schema Validation" data set (and the "Custom Nodes" data set).
406
-
407
- An example `onUpdate` validation function (using Ajv) could be something like this:
408
-
409
- ```js
410
- import { JsonEditor } from 'json-edit-react'
411
- import Ajv from 'ajv'
412
- import schema from './my-json-schema.json'
413
-
414
- const ajv = new Ajv()
415
- const validate = ajv.compile(schema)
416
-
417
- /// Etc....
418
-
419
- // In the React component:
420
- return
421
- <JsonEditor
422
- data={ jsonData }
423
- onUpdate={ ({ newData }) => {
424
- const valid = validate(newData)
425
- if (!valid) {
426
- console.log('Errors', validate.errors)
427
- const errorMessage = validate.errors
428
- ?.map((error) => `${error.instancePath}${error.instancePath ? ': ' : ''}${error.message}`)
429
- .join('\n')
430
- // Send detailed error message to an external UI element, such as a "Toast" notification
431
- displayError({
432
- title: 'Not compliant with JSON Schema',
433
- description: errorMessage,
434
- status: 'error',
435
- })
436
- // This string returned to and displayed in json-edit-react UI
437
- return 'JSON Schema error'
438
- }
439
- }}
440
- { ...otherProps } />
441
- ```
442
-
443
- ### Drag-n-drop
444
-
445
- The `restrictDrag` property controls which items (if any) can be dragged into new positions. By default, this is *off*, so you must set `restrictDrag = false` to enable this functionality. Like the Edit restrictions above, this property can also take a Filter function for fine-grained control. There are a couple of additional considerations, though:
446
-
447
- - Javascript does *not* guarantee object property order, so enabling this feature may yield unpredictable results. See [here](https://dev.to/frehner/the-order-of-js-object-keys-458d) for an explanation of how key ordering is handled. It is strongly advised that you only enable drag-and-drop functionality if:
448
- 1. you're sure object keys will always be simple strings (i.e. not digits or non-standard characters)
449
- 2. you're saving the data in a serialisation format that preserves key order. For example, storing in a Postgres database using the `jsonb` (binary JSON) type, key order is meaningless, so the next time the object is loaded, the keys will be listed alphabetically.
450
- - The `restrictDrag` filter applies to the *source* element (i.e. the node being dragged), not the destination.
451
- - To be draggable, the node must *also* be delete-able (via the `restrictDelete` prop), as dragging a node to a new destination is essentially just deleting it and adding it back elsewhere.
452
- - Similarly, the destination collection must be editable in order to drop it in there. This means that, if you've gone to the trouble of configuring restrictive editing constraints using Filter functions, you can be confident that they can't be circumvented via drag-n-drop.
453
-
454
- ## Full object editing
455
-
456
- The user can edit the entire JSON object (or a sub-node) as raw text (provided you haven't restricted it using a [`restrictEdit` function](#filter-functions)). By default, we just display a native HTML [textarea](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea) element for plain-text editing. However, you can offer a more sophisticated text/code editor by passing the component into the `TextEditor` prop. Your component must provide the following props for json-edit-react to use:
457
-
458
- - `value: string` — the current text
459
- - `onChange: (value: string) => void` — should be called on every keystroke to update `value`
460
- - `onKeyDown: (e: React.KeyboardEvent) => void` — should be called on every keystroke to detect "Accept"/"Cancel" keys
461
-
462
- You can see an example in the [demo](https://carlosnz.github.io/json-edit-react/) where I have implemented [**CodeMirror**](https://codemirror.net/) when the "Custom Text Editor" option is checked. It changes the native editor (on the left) into the one shown on the right:
463
-
464
- <img width="800" alt="Text editor comparison" src="image/text-editor-comparison.png">
465
-
466
- See the codebase for the exact implementation details:
467
-
468
- - [Simple component that wraps CodeMirror](https://github.com/CarlosNZ/json-edit-react/blob/main/demo/src/CodeEditor.tsx)
469
- - [Prop passed to json-edit-react](https://github.com/CarlosNZ/json-edit-react/blob/d6e3c39d1fe876fa8ed267301ebecf128132b602/demo/src/App.tsx#L450-L465)
470
-
471
- ## Search/Filtering
472
-
473
- The displayed data can be filtered based on search input from a user. The user input should be captured independently (we don't provide a UI here) and passed in with the `searchText` prop. This input is debounced internally (time can be set with the `searchDebounceTime` prop), so no need for that as well. The values that the `searchText` are tested against is specified with the `searchFilter` prop. By default (no `searchFilter` defined), it will match against the data *values* (with case-insensitive partial matching — i.e. input "Ilb", will match value "Bilbo").
474
-
475
- You can specify what should be matched by setting `searchFilter` to either `"key"` (match property names), `"value"` (the default described above), or `"all"` (match both properties and values). This should be enough for the majority of use cases, but you can specify your own `SearchFilterFunction`. The search function is the same signature as the above [FilterFunctions](#filter-functions) but takes one additional argument for the `searchText`, i.e.
476
-
477
- ```ts
478
- ( { key, path, level, value, ...etc }:FilterFunctionInput, searchText:string ) => boolean
479
- ```
480
-
481
- There are two helper functions (`matchNode()` and `matchNodeKey()`) exported with the package that might make creating your search function easier (these are the functions used internally for the `"key"` and `"value"` matches described above). You can see what they do [here](https://github.com/CarlosNZ/json-edit-react/blob/574f2c1ba3e724c93ce8ab9cdba2fe8ebbbbf806/src/filterHelpers.ts#L64-L95).
482
-
483
- An example custom search function can be seen in the [Demo](#https://carlosnz.github.io/json-edit-react/?data=jsonPlaceholder) with the "Client list" data set -- the search function matches by name and username, and makes the entire "Client" object visible when one of those matches, so it can be used to find a particular person and edit their specific details:
484
-
485
- ```js
486
- ({ path, fullData }, searchText) => {
487
- // Matches *any* node that shares a path (i.e. a descendent) with a matching name/username
488
- if (path?.length >= 2) {
489
- const index = path?.[0]
490
- return (
491
- matchNode({ value: fullData[index].name }, searchText) ||
492
- matchNode({ value: fullData[index].username }, searchText)
493
- )
494
- } else return false
495
- }
496
- ```
497
-
498
- ## Themes & Styles
499
-
500
- There is a small selection of built-in themes (as seen in the [Demo app](https://carlosnz.github.io/json-edit-react/)). In order to use one of these, just import it from the package and pass it as the theme prop:
501
-
502
- ```js
503
- import { JsonEditor, githubDarkTheme } from 'json-edit-react'
504
- // ...other imports
505
-
506
- const MyApp = () => {
507
- const [ data, setData ] = useState({ one: 1, two: 2 })
508
-
509
- return <JsonEditor
510
- data={data}
511
- setData={setData}
512
- theme={githubDarkTheme}
513
- // other props...
514
- />
515
- }
516
- ```
517
-
518
- The following themes are available in the package (although realistically, these exist more to showcase the capabilities — I'm open to better built-in themes, so feel free to [create an issue](https://github.com/CarlosNZ/json-edit-react/issues) with suggestions):
519
- - `githubDarkTheme`
520
- - `githubLightTheme`
521
- - `monoDarkTheme`
522
- - `monoLightTheme`
523
- - `candyWrapperTheme`
524
- - `psychedelicTheme`
525
-
526
- However, you can pass in your own theme object, or part thereof. The theme structure is as follows (this is the "default" theme definition):
527
-
528
- ```js
529
- {
530
- displayName: 'Default',
531
- fragments: { edit: 'rgb(42, 161, 152)' },
532
- styles: {
533
- container: {
534
- backgroundColor: '#f6f6f6',
535
- fontFamily: 'monospace',
536
- },
537
- collection: {},
538
- collectionInner: {},
539
- collectionElement: {},
540
- dropZone: {},
541
- property: '#292929',
542
- bracket: { color: 'rgb(0, 43, 54)', fontWeight: 'bold' },
543
- itemCount: { color: 'rgba(0, 0, 0, 0.3)', fontStyle: 'italic' },
544
- string: 'rgb(203, 75, 22)',
545
- number: 'rgb(38, 139, 210)',
546
- boolean: 'green',
547
- null: { color: 'rgb(220, 50, 47)', fontVariant: 'small-caps', fontWeight: 'bold' },
548
- input: ['#292929', { fontSize: '90%' }],
549
- inputHighlight: '#b3d8ff',
550
- error: { fontSize: '0.8em', color: 'red', fontWeight: 'bold' },
551
- iconCollection: 'rgb(0, 43, 54)',
552
- iconEdit: 'edit',
553
- iconDelete: 'rgb(203, 75, 22)',
554
- iconAdd: 'edit',
555
- iconCopy: 'rgb(38, 139, 210)',
556
- iconOk: 'green',
557
- iconCancel: 'rgb(203, 75, 22)',
558
- },
559
- }
560
-
561
- ```
562
-
563
- The `styles` property is the main one to focus on. Each key (`property`, `bracket`, `itemCount`) refers to a part of the UI. The value for each key is *either*:
564
- - a `string`, in which case it is interpreted as the colour (or background colour in the case of `container` and `inputHighlight`)
565
- - a full CSS style object for fine-grained definition. You only need to provide properties you wish to override — all unspecified ones will fallback to either the default theme, or another theme that you specify as the "base".
566
- - a "Style Function", which is a function that takes the same input as [Filter Functions](#filter-functions), but returns a CSS style object (or `null`). This allows you to *dynamically* change styling of various elements based on content or structure. (An example is in the [Demo](https://carlosnz.github.io/json-edit-react/?data=customNodes) "Custom Nodes" data set, where the character names are styled larger than other string values)
567
- - an array containing any combination of the above, in which case they are merged together. For example, you could provide a Theme Function with styling for a very specific condition, but then provide "fallback" styles whenever the function returns `null`. (In the array, the *later* items have higher precedence)
568
-
569
- For a simple example, if you want to use the "githubDark" theme, but just change a couple of small things, you'd specify something like this:
570
-
571
- ```js
572
- // in <JsonEditor /> props
573
- theme={[
574
- githubDarkTheme,
575
- {
576
- iconEdit: 'grey',
577
- boolean: { color: 'red', fontStyle: 'italic', fontWeight: 'bold', fontSize: '80%' },
578
- },
579
- ]}
580
- ```
581
-
582
- Which would change the "Edit" icon and boolean values from this:
583
- <img width="218" alt="Github Dark theme original" src="image/theme_edit_before.png">
584
- into this:
585
- <img width="218" alt="Github Dark theme modified" src="image/theme_edit_after.png">
586
-
587
- Or you could create your own theme from scratch and overwrite the whole theme object.
588
-
589
- So, to summarise, the `theme` prop can take *either*:
590
-
591
- - an imported theme, e.g `"candyWrapperTheme"`
592
- - a theme object:
593
- - can be structured as above with `fragments`, `styles`, `displayName` etc., or just the `styles` part (at the root level)
594
- - a theme name *and* an override object in an array, i.e. `[ "<themeName>, {...overrides } ]`
595
-
596
- You can play round with live editing of the themes in the [Demo app](https://carlosnz.github.io/json-edit-react/) by selecting "Edit this theme!" from the "Demo data" selector (though you won't be able to create functions in JSON).
597
-
598
- #### CSS classes
599
-
600
- Another way to style the component is to target the CSS classes directly. Every element in the component has a unique class name, so you should be able to locate them in your browser inspector and override them accordingly. All class names begin with the prefix `jer-`, e.g. `jer-collection-header-row`, `jer-value-string`.
601
-
602
- ### Fragments
603
-
604
- The `fragments` property above is just a convenience to allow repeated style "fragments" to be defined once and referred to using an alias. For example, if you wanted all your icons to be blue and slightly larger and spaced out, you might define a fragment like so:
605
- ```js
606
- fragments: { iconAdjust: { color: "blue", fontSize: "110%", marginRight: "0.6em" }}
607
- ```
608
-
609
- Then in the theme object, just use:
610
- ```js
611
- {
612
- ...,
613
- iconEdit: "iconAdjust",
614
- iconDelete: "iconAdjust",
615
- iconAdd: "iconAdjust",
616
- iconCopy: "iconAdjust",
617
- }
618
- ```
619
-
620
- Then, when you want to tweak it later, you only need to update it in one place!
621
-
622
- Fragments can also be mixed with additional properties, and even other fragments, like so:
623
- ```js
624
- iconEdit: [ "iconAdjust", "anotherFragment", { marginLeft: "1em" } ]
625
- ```
626
-
627
- ### A note about sizing and scaling
628
-
629
- Internally, all sizing and spacing is done in `em`s, never `px` (aside from the [`rootFontSize`](#look-and-feel--ui), which sets the "base" size). This makes scaling a lot easier — just change the `rootFontSize` prop (or set `fontSize` on the main container via targeting the class, or tweaking the [theme](#themes--styles)), and watch the *whole* component scale accordingly.
630
-
631
- ### Icons
632
-
633
- The default icons can be replaced, but you need to provide them as React/HTML elements. Just define any or all of them within the `icons` prop, keyed as follows:
634
-
635
- ```js
636
- icons={{
637
- add: <YourIcon />
638
- edit: <YourIcon />
639
- delete: <YourIcon />
640
- copy: <YourIcon />
641
- ok: <YourIcon />
642
- cancel: <YourIcon />
643
- chevron: <YourIcon />
644
- }}
645
- ```
646
-
647
- The Icon components will need to have their own styles defined, as the theme styles *won't* be added to the custom elements.
648
-
649
- ## Localisation
650
-
651
- Localise your implementation by passing in a `translations` object to replace the default strings. The keys and default (English) values are as follows:
652
- ```js
653
- {
654
- ITEM_SINGLE: '{{count}} item',
655
- ITEMS_MULTIPLE: '{{count}} items',
656
- KEY_NEW: 'Enter new key',
657
- ERROR_KEY_EXISTS: 'Key already exists',
658
- ERROR_INVALID_JSON: 'Invalid JSON',
659
- ERROR_UPDATE: 'Update unsuccessful',
660
- ERROR_DELETE: 'Delete unsuccessful',
661
- ERROR_ADD: 'Adding node unsuccessful',
662
- DEFAULT_STRING: 'New data!',
663
- DEFAULT_NEW_KEY: 'key',
664
- SHOW_LESS: '(Show less)',
665
- }
666
-
667
- ```
668
-
669
- ## Custom Nodes
670
-
671
- You can replace certain nodes in the data tree with your own custom components. An example might be for an image display, or a custom date editor, or just to add some visual bling. See the "Custom Nodes" data set in the [interactive demo](https://carlosnz.github.io/json-edit-react/?data=customNodes) to see it in action. (There is also a custom Date picker that appears when editing ISO strings in the other data sets.)
672
-
673
- Custom nodes are provided in the `customNodeDefinitions` prop, as an array of objects of following structure:
674
-
675
- ```js
676
- {
677
- condition, // a FilterFunction, as above
678
- element, // React Component
679
- customNodeProps, // object (optional)
680
- hideKey, // boolean (optional)
681
- defaultValue, // JSON value for a new instance of your component
682
- showOnEdit // boolean, default false
683
- showOnView // boolean, default true
684
- showEditTools // boolean, default true
685
- name // string (appears in Types selector)
686
- showInTypesSelector // boolean (optional), default false
687
- passOriginalNode // boolean (optional), default false -- if `true` makes the original node
688
- // for rendering within Custom Node
689
-
690
- // Only affects Collection nodes:
691
- showCollectionWrapper // boolean (optional), default true
692
- wrapperElement // React component (optional) to wrap *outside* the normal collection wrapper
693
- wrapperProps // object (optional) -- props for the above wrapper component
694
- }
695
- ```
696
-
697
- The `condition` is just a [Filter function](#filter-functions), with the same input parameters (`key`, `path`, `value`, etc.), and `element` is a React component. Every node in the data structure will be run through each condition function, and any that match will be replaced by your custom component. Note that if a node matches more than one custom definition conditions (from multiple components), the *first* one will be used, so place them in the array in priority order.
698
-
699
- The component will receive *all* the same props as a standard node component plus some additional ones — see [BaseNodeProps](https://github.com/CarlosNZ/json-edit-react/blob/b085f6391dabf574809f1040b11401c13344923d/src/types.ts#L219-L265) (common to all nodes) and [CustomNodeProps](https://github.com/CarlosNZ/json-edit-react/blob/b085f6391dabf574809f1040b11401c13344923d/src/types.ts#L275-L287) type definitions. Specifically, if you want to update the data structure from your custom node, you'll need to call the `setValue` method on your node's data value. And if you enable `passOriginalNode` above, you'll also have access to `originalNode` and `originalNodeKey` in order to render the standard content (i.e. what would have been rendered if it wasn't intercepted by this Custom Node) -- this can be helpful if you want your Custom Node to just be the default content with a little extra decoration. (*Note:* you may need a little custom CSS to render these original node components identically to the default display.)
700
-
701
- You can pass additional props specific to your component, if required, through the `customNodeProps` object. A thorough example of a custom **Date Picker** is used in the demo (along with a couple of other more basic presentational ones), which you can inspect to see how to utilise the standard props and a couple of custom props. View the source code [here](https://github.com/CarlosNZ/json-edit-react/blob/main/demo/src/customComponents/DateTimePicker.tsx).
702
-
703
- By default, your component will be presented to the right of the property key it belongs to, like any other value. However, you can hide the key itself by setting `hideKey: true`, and the custom component will take the whole row. (See the "Presented by" box in the "Custom Nodes" data set for an example.)
704
-
705
- Also, by default, your component will be treated as a "display" element, i.e. it will appear in the JSON viewer, but when editing, it will revert to the standard editing interface. This can be changed, however, with the `showOnEdit`, `showOnView` and `showEditTools` props. For example, a Date picker might only be required when *editing* and left as-is for display. The `showEditTools` prop refers to the editing icons (copy, add, edit, delete) that appear to the right of each value on hover. If you choose to disable these but you still want to your component to have an "edit" mode, you'll have to provide your own UI mechanism to toggle editing.
706
-
707
- You can allow users to create new instances of your special nodes by selecting them as a "Type" in the types selector when editing/adding values. Set `showInTypesSelector: true` to enable this. However, if this is enabled you need to also provide a `name` (which is what the user will see in the selector) and a `defaultValue` which is the data that is inserted when the user selects this "type". (The `defaultValue` must return `true` if passed through the `condition` function in order for it to be immediately displayed using your custom component.)
708
-
709
- ### Active hyperlinks
710
-
711
- A simple custom component and definition to turn url strings into clickable links is provided with the main package for you to use out of the box. Just import and use like so:
712
-
713
- ```js
714
- import { JsonEditor, LinkCustomNodeDefinition } from 'json-edit-react'
715
-
716
- // ...Other stuff
717
- return (
718
- <JsonEditor
719
- {...otherProps}
720
- customNodeDefinitions={[LinkCustomNodeDefinition, ...otherCustomDefinitions]}
721
- />
722
- )
723
- ```
724
-
725
- ### Custom Collection nodes
726
-
727
- In most cases it will be preferable (and simpler) to create custom nodes to match *value* nodes (i.e. not `array` or `object` *collection* nodes), which is what all the [Demo](https://carlosnz.github.io/json-edit-react/?data=customNodes) examples show. However, if you *do* wish to target a whole collection node, there are a couple of other things to know:
728
- - The normal descendants of this node can still be displayed using the [React `children`](https://react.dev/learn/passing-props-to-a-component#passing-jsx-as-children) property, it just becomes your component's responsibility to handle it.
729
- - You can specify two different components in the definition:
730
- - the regular `element` prop, which will be displayed *inside* the collection brackets (i.e. it appears as the *contents* of the collection)
731
- - an optional `wrapperElement`, which is displayed *outside* the collection (props can be supplied as described above with `wrapperProps`). Again, the inner contents (including your custom `element`) can be displayed using React `children`. In this example, the **blue** border shows the `wrapperElement` and the **red** border shows the inner `element`:
732
- <img width="450" alt="custom node levels" src="image/custom_component_levels.png">
733
- - There is one additional prop, `showCollectionWrapper` (default `true`), which, when set to `false`, hides the surrounding collection elements (namely the hide/show chevron and the brackets). In this case, you would have to provide your own hide/show mechanism in your component should you want it.
734
-
735
-
736
- ## Custom Text
737
-
738
- It's possible to change the various text strings displayed by the component. You can [localise it](#localisation), but you can also specify functions to override the displayed text based on certain conditions. For example, say we want the property count text (e.g. `6 items` by default) to give a summary of a certain type of node, which can look nice when collapsed. For example (taken from the [Demo](https://carlosnz.github.io/json-edit-react/?data=customNodes)):
739
-
740
- <img width="391" alt="Custom text example" src="image/custom_text.png">
741
-
742
- The `customText` property takes an object, with any of the [localisable keys](#localisation) as keys, with a function that returns a string (or `null`, which causes it to fallback to the localised or default string). The input to these functions is the same as for [Filter functions](#filter-functions), so in this example, it would be defined like so:
743
-
744
- ```js
745
-
746
- // The function definition
747
- const itemCountReplacement = ({ key, value, size }) => {
748
- // This returns "Steve Rogers (Marvel)" for the node summary
749
- if (value instanceof Object && 'name' in value)
750
- return `${value.name} (${(value)?.publisher ?? ''})`
751
- // This returns "X names" for the alias lists
752
- if (key === 'aliases' && Array.isArray(value))
753
- return `${size} ${size === 1 ? 'name' : 'names'}`
754
- // Everything else as normal
755
- return null
756
- }
757
-
758
- // And in component props...
759
- ...otherProps,
760
- customText = {
761
- ITEM_SINGLE: itemCountReplacement,
762
- ITEMS_MULTIPLE: itemCountReplacement,
763
- }
764
- ```
765
-
766
- ## Custom Buttons
767
-
768
- In addition to the "Copy", "Edit" and "Delete" buttons that appear by each value, you can add your own buttons if you need to allow some custom operations on the data. Provide an array of button definitions in the `customButtons` prop, with the following definition structure:
769
-
770
- ```ts
771
- {
772
- Element: React.FC<{ nodeData: NodeData }>,
773
- onClick?: (nodeData: NodeData, e: React.MouseEvent) => void
774
- }
775
- ```
776
- Where `NodeData` is the same data structure received by the previous "Update Functions".
777
-
778
- The `onClick` is optional -- don't provide it if you have your own `onClick` handler within your button component.
779
-
780
- ## Keyboard customisation
781
-
782
- The default keyboard controls are [outlined above](#usage), but it's possible to customise/override these. Just pass in a `keyboardControls` prop with the actions you wish to override defined. The default config object is:
783
- ```ts
784
- {
785
- confirm: 'Enter', // default for all Value nodes, and key entry
786
- cancel: 'Escape',
787
- objectConfirm: { key: 'Enter', modifier: ['Meta', 'Shift', 'Control'] },
788
- objectLineBreak: 'Enter',
789
- stringConfirm: 'Enter',
790
- stringLineBreak: { key: 'Enter', modifier: 'Shift' },
791
- numberConfirm: 'Enter',
792
- numberUp: 'ArrowUp',
793
- numberDown: 'ArrowDown',
794
- tabForward: 'Tab',
795
- tabBack: { key: 'Tab', modifier: 'Shift' },
796
- booleanConfirm: 'Enter',
797
- booleanToggle: ' ', // Space bar
798
- clipboardModifier: ['Meta', 'Control'],
799
- collapseModifier: 'Alt',
800
- }
801
- ```
802
-
803
- If (for example), you just wish to change the general "confirmation" action to "Cmd-Enter" (on Mac), or "Ctrl-Enter", you'd just pass in:
804
- ```ts
805
- keyboardControls = {
806
- confirm: {
807
- key: "Enter",
808
- modifier: [ "Meta", "Control" ]
809
- }
810
- }
811
- ```
812
-
813
- **Considerations**:
814
-
815
- - Key names come from [this list](https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values)
816
- - Accepted modifiers are "Meta", "Control", "Alt", "Shift"
817
- - On Mac, "Meta" refers to the "Cmd" key, and "Alt" refers to "Option"
818
- - If multiple modifiers are specified (in an array), *any* of them will be accepted (multi-modifier commands not currently supported)
819
- - You only need to specify values for `stringConfirm`, `numberConfirm`, and `booleanConfirm` if they should *differ* from your `confirm` value.
820
- - You won't be able to override system or browser behaviours: for example, on Mac "Ctrl-click" will perform a right-click, so using it as a click modifier won't work (hence we also accept "Meta"/"Cmd" as the default `clipboardModifier`).
821
-
822
- ## External control
823
-
824
- You can interact with the component externally, with event callbacks and triggers to set/get the collapse or editing state of any node.
825
-
826
- ### Event callbacks
827
-
828
- Pass in a function to the props `onEditEvent` and `onCollapse` if you want your app to be able to respond to these events.
829
-
830
- The `onEditEvent` callback is executed whenever the user starts or stops editing a node, and has the following signature:
831
-
832
- ```ts
833
- type OnEditEventFunction =
834
- (path: CollectionKey[] | null, isKey: boolean) => void
835
- ```
836
-
837
- The `path` will be an array representing the path components when starting to edit, and `null` when ending the edit. The `isKey` indicates whether the edit is for the property `key` rather than `value`.
838
-
839
- The `onCollapse` callback is executed when user opens or collapses a node, and has the following signature:
840
-
841
- ```ts
842
- type OnCollapseFunction = (
843
- {
844
- path: CollectionKey[],
845
- collapsed: boolean, // closing = true, opening = false
846
- includeChildren: boolean // if was clicked with Modifier key to
847
- // open/close all descendants as well
848
- }
849
- ) => void
850
- ```
851
-
852
- ### Event triggers
853
-
854
- You can *trigger* collapse and editing actions by changing the the `externalTriggers` prop.
855
-
856
- The shape of the `externalTriggers` object is:
857
-
858
- ```ts
859
- interface ExternalTriggers {
860
- collapse?: CollapseState | CollapseState[]
861
- edit?: EditState
862
- }
863
-
864
- // CollapseState same as `onCollapseFunction` (above) input
865
- interface CollapseState {
866
- path: CollectionKey[]
867
- collapsed: boolean
868
- includeChildren: boolean
869
- }
870
-
871
- interface EditState {
872
- path?: CollectionKey[]
873
- action?: 'accept' | 'cancel'
874
- }
875
- ```
876
-
877
- For the `edit` trigger, the `path` is only required when *starting* to edit, and
878
- the `action` is only required when *stopping* the edit, to determine whether the
879
- component should cancel or submit the current changes.
880
-
881
- <div style="padding: 8px; border-left: 4px solid #888; background-color: #f8f8f8; margin: 10px 0;">
882
- ⚠️ **Warning**
883
-
884
- Ensure that your `externalTriggers` object is stable (i.e. doesn't create new instances on each render) so as to not cause unwanted triggering -- you may need to wrap it in `useMemo`.
885
- You should also be careful that your event callbacks and triggers don't cause an infinite loop!
886
- </div>
887
-
888
-
889
- ## Undo functionality
890
-
891
- Even though Undo/Redo functionality is probably desirable in most cases, this is not built in to the component, for two main reasons:
892
- 1. It would involve too much additional UI and I didn't want this component becoming opinionated about the look and feel beyond the essentials (which are mostly customisable/style-able anyway)
893
- 2. It is quite straightforward to implement using existing libraries. I've used **[use-undo](https://github.com/homerchen19/use-undo)** in the [Demo](https://carlosnz.github.io/json-edit-react/), which is working well.
894
-
895
- ## Exported helpers
896
-
897
- A few helper functions, components and types that might be useful in your own implementations (from creating Filter or Update functions, or Custom components) are exported from the package:
898
-
899
- ### Functions & Components
900
-
901
- - `themes`: an object containing all the built-in theme definitions
902
- - `LinkCustomComponent`: the component used to render [hyperlinks](#active-hyperlinks)
903
- - `LinkCustomNodeDefinition`: custom node definition for [hyperlinks](#active-hyperlinks)
904
- - `StringDisplay`: main component used to display a string value, re-used in the above "Link" Custom Component
905
- - `IconAdd`, `IconEdit`, `IconDelete`, `IconCopy`, `IconOk`, `IconCancel`, `IconChevron`: all the built-in [icon](#icons) components
906
- - `matchNode`, `matchNodeKey`: helpers for defining custom [Search](#searchfiltering) functions
907
- - `extract`: function to extract a deeply nested object value from a string path. See [here](https://github.com/CarlosNZ/object-property-extractor)
908
- - `assign`: function to set a deep object value from a string path. See [here](https://github.com/CarlosNZ/object-property-assigner)
909
-
910
- ### Types
911
-
912
- - `Theme`: a full [Theme](#themes--styles) object
913
- - `ThemeInput`: input type for the `theme` prop
914
- - `JsonEditorProps`: all input props for the Json Editor component
915
- - `JsonData`: main `data` object -- any valid JSON structure
916
- - [`UpdateFunction`](#update-functions), [`OnChangeFunction`](#onchange-function), [`OnErrorFunction`](#onerror-function) [`FilterFunction`](#filter-functions), [`CopyFunction`](#copy-function), [`SearchFilterFunction`](#searchfiltering), [`OnEditEventFunction`](#event-callbacks), [`OnCollapseFunction`](#event-callbacks), [`CompareFunction`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort),[`TypeFilterFunction`](#filter-functions), [`LocalisedString`](#localisation), [`CustomNodeDefinition`](#custom-nodes), [`CustomTextDefinitions`](#custom-text), [`CustomTextFunction`](#custom-text), [`ExternalTriggers`](#event-triggers)
917
- - `TranslateFunction`: function that takes a [localisation](#localisation) key and returns a translated string
918
- - `IconReplacements`: input type for the `icons` prop
919
- - `CollectionNodeProps`: all props passed internally to "collection" nodes (i.e. objects/arrays)
920
- - `ValueNodeProps`: all props passed internally to "value" nodes (i.e. *not* objects/arrays)
921
- - `CustomNodeProps`: all props passed internally to [Custom nodes](#custom-nodes); basically the same as `CollectionNodeProps` with an extra `customNodeProps` field for passing props unique to your component`
922
- - `DataType`: `"string"` | `"number"` | `"boolean"` | `"null"` | `"object"` | `"array"`
923
- - `KeyboardControls`: structure for [keyboard customisation](#keyboard-customisation) prop
924
- - `TextEditorProps`: props for custom [Text Editor](#full-object-editing)
925
-
926
- ## Issues, bugs, suggestions?
927
-
928
- Please open an issue: https://github.com/CarlosNZ/json-edit-react/issues
929
-
930
- ## Roadmap
931
-
932
- The main features I'd like to introduce are:
933
-
934
- 1. ~~**JSON Schema validation**. We can currently specify a reasonable degree of control over what can be edited using [Filter functions](#filter-functions) with the restriction props, but I'd like to go a step further and be able to pass in a [JSON Schema](https://json-schema.org/) and have the data be automatically validated against it, with the results reflected in the UI. This would allow control over data types and prevent missing properties, something that is not currently possible.~~ 👍 [Done](#json-schema-validation) (using 3rd-party validation library)
935
- 2. ~~**Search/Visibility filter** — allow the user to narrow the list of visible keys with a simple search input. This would be useful for very large data objects, but is possibly getting a bit too much in terms of opinionated UI, so would need to ensure it can be styled easily. Perhaps it would be better if the "Search" input was handled outside this package, and we just accepted a "search" string prop?~~ 👍 [Done](#searchfiltering)
936
-
937
- ## Inspiration
938
-
939
- This component is heavily inspired by [react-json-view](https://github.com/mac-s-g/react-json-view), a great package that I've used in my own projects. However, it seems to have been abandoned now, and requires a few critical fixes, so I decided to create my own from scratch and extend the functionality while I was at it.
940
-
941
- ## Changelog
94
+ ---
942
95
 
943
- - **1.24.0**:
944
- - Option to access (and render) the original node (and its key) within a [Custom Node](#custom-nodes) ([#180](https://github.com/CarlosNZ/json-edit-react/issues/180))
945
- - Cancelling edit after changing type correctly reverts to previous value ([#122](https://github.com/CarlosNZ/json-edit-react/issues/122))
946
- - **1.23.1**: Fix bug where you could collapse a node by clicking inside a "new key" input field [#175](https://github.com/CarlosNZ/json-edit-react/issues/175)
947
- - **1.23.0**:
948
- - Add `viewOnly` prop as a shorthand for restricting all editing [#168](https://github.com/CarlosNZ/json-edit-react/issues/168)
949
- - Add a toggle on the "..." of long strings so they can be expanded to full length in the UI [#172](https://github.com/CarlosNZ/json-edit-react/issues/172)
950
- - **1.22.5**: Fix for crash when trying to switch to object type if new data is rejected by `onUpdate` function [#169](https://github.com/CarlosNZ/json-edit-react/issues/169) (thanks @kyaw-t) [#170](https://github.com/CarlosNZ/json-edit-react/pulls/170)
951
- - **1.22.2**: Make `collapseAnimationTime` use local value rather than global CSS variable [#163](https://github.com/CarlosNZ/json-edit-react/issues/163)
952
- - **1.22.1**: Fix custom nodes not re-calculating condition when `data` changes
953
- - **1.22.0**:
954
- - Option for [custom text/code editor](#full-object-editing) when editing full JSON object [#157](https://github.com/CarlosNZ/json-edit-react/issues/157)
955
- - Handle clipboard copy errors [#159](https://github.com/CarlosNZ/json-edit-react/pull/159) (thanks @dm-xai) [#160](https://github.com/CarlosNZ/json-edit-react/issues/160)
956
- - **1.21.1**: Users can now navigate between nodes using "Tab"/"Shift-Tab" key
957
- - **1.20.0**: Refactor out direct access of global `document` object, which allows component to work with server-side rendering
958
- - **1.19.2**:
959
- - Boolean toggle key can be customised [#150](https://github.com/CarlosNZ/json-edit-react/issues/150)
960
- - Pass `nodeData` to [custom buttons](#custom-buttons) [#146](https://github.com/CarlosNZ/json-edit-react/issues/146)
961
- - **1.19.0**: Built-in [themes](#themes--styles) must now be imported separately -- this improves tree-shaking to prevent unused themes being bundled with your build
962
- - **1.18.0**:
963
- - Ability to [customise keyboard controls](#keyboard-customisation)
964
- - Option to insert new values at the top
965
- - **1.17.0**: `defaultValue` function takes the new `key` as second parameter
966
- - **1.16.0**: Extend the "click" zone for collapsing nodes to the header bar and left margin (not just the collapse icon)
967
- - **1.15.12**:
968
- - [Custom buttons](#custom-buttons)
969
- - Misc small bug fixes
970
- - **1.15.7**:
971
- - Small bug fix for `overflow: clip` setting based on animating
972
- state
973
- - Small tweak to outer bracket positioning
974
- - **1.15.5**: Bug fix for collapse icon being clipped when indent is low #104
975
- - **1.15.3**:
976
- - Allow [UpdateFunction](#update-functions) to return `true` to represent success
977
- - Refactor collapse animation to improve lag and accuracy
978
- - **1.15.2**:
979
- - Collapse animation timing is configurable (#96)
980
- - Bug fix for non-responsive keyboard submit for boolean values (#97)
981
- - **1.15.0**: Remove ([JSON5](https://json5.org/)) from the package, and provided props for passing in *any* alternative JSON parsing and stringifying methods.
982
- - **1.14.0**:
983
- - Allow [UpdateFunction](#update-functions) to return a modified value, not just an error
984
- - Add `setData` prop to discourage reliance on internal data [state management](#managing-state)
985
- - Refactor state/event management to use less `useEffect` hooks
986
- - **1.13.3**: Bug fix for when root data value is `null` [#90](https://github.com/CarlosNZ/json-edit-react/issues/90)
987
- - **1.13.2**: Slightly better error handling when validating [JSON schema](#json-schema-validation)
988
- - **1.13.0**:
989
- - [Drag-n-drop](#drag-n-drop) editing!
990
- - Remove unnecessary dependency
991
- - Refactor some duplicate code into common hook
992
- - **1.12.0**:
993
- - Preserve editing mode when changing Data Type
994
- - [`onError` callback](#onerror-function) available for custom error handling
995
- - **1.11.8**: Fix regression for empty data root name introduces in 1.11.7
996
- - **1.11.7**: Handle \<empty-string\> object keys / prevent duplicate keys
997
- - **1.11.3**: Bug fix for invalid state when changing type to Collection node
998
- - **1.11.0**:
999
- - Improve CSS definitions to prevent properties from being overridden by the host environment's CSS
1000
- - Add `rootFontSize` prop to set the "base" size for the component
1001
- - **1.10.2**:
1002
- - Fixes for text wrapping and content overlaps when values and inputs contain very long strings (#57, #58)
1003
- - Only allow one element to be edited at a time, and prevent collapsing when an inner element is being edited.
1004
- - **1.9.0**:
1005
- - Increment number input using up/down arrow keys
1006
- - Option to display string values without "quotes"
1007
- - Add [`onChange` prop](#onchange-function) to allow validation/restriction of user input as they type
1008
- - Don't update `data` if user hasn't actually changed a value (prevents Undo from being unnecessarily triggered)
1009
- - Misc HTML warnings, React compatibility fixes
1010
- - **1.8.0**: Further improvements/fixes to collection custom nodes, including additional `wrapperElement` [prop](#custom-collection-nodes)
1011
- - Add optional `id` prop
1012
- - **1.7.2**:
1013
- - Fix and improve Custom nodes in *collections*
1014
- - Include `index` in Filter (and other) function input
1015
- - **1.7.0**: Implement [Search/filtering](#searchfiltering) of data visibility
1016
- - **1.6.1**: Revert data state on Update Function error
1017
- - **1.6.0**: Allow a function for `defaultValue` prop
1018
- - **1.5.0**:
1019
- - Open/close all descendant nodes by holding "Alt"/"Option" while opening/closing a node
1020
- - **1.4.0**:
1021
- - [Style functions](#themes--styles) for context-dependent styling
1022
- - Handle "loose" ([JSON5](https://json5.org/)) JSON text input(e.g. non-quoted keys, trailing commas, etc.)
1023
- - **1.3.0**:
1024
- - [Custom (dynamic) text](#custom-text)
1025
- - Add [hyperlink](#custom-nodes) Custom component to bundle
1026
- - Better indentation of collection nodes (property name lines up with non-collection nodes, not the collapse icon)
1027
- - **1.2.2**: Allow editing of Custom nodes
1028
- - **1.1.0**: Don't manage data state within component
1029
- - **1.0.0**:
1030
- - [Custom nodes](#custom-nodes)
1031
- - Allow editing of keys
1032
- - Option to define restrictions on data type selection
1033
- - Option to hide array/object item counts
1034
- - Improve keyboard interaction
1035
- - **0.9.6**: Performance improvement by not processing child elements if not visible
1036
- - **0.9.4**:
1037
- - Layout improvements
1038
- - Better internal handling of functions in data
1039
- - **0.9.3**: Bundle as ES6 module
1040
- - **0.9.1**: Export more Types from the package
1041
- - **0.9.0**: Initial release
96
+ For **FULL DOCUMENTATION**, visit [https://github.com/CarlosNZ/json-edit-react](https://github.com/CarlosNZ/json-edit-react?tab=readme-ov-file#json-edit-react)