json-edit-react 0.9.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 ADDED
@@ -0,0 +1,334 @@
1
+ # json-edit-react
2
+
3
+ <img width="60" alt="screenshot" src="image/logo192.png" style="float:left; margin-right: 1em;">
4
+
5
+ A [React](https://github.com/facebook/react) component for editing or viewing JSON/object data
6
+
7
+ Features include:
8
+
9
+ - edit individual values, or whole objects as JSON text
10
+ - fine-grained control over which elements can be edited, deleted, or added to
11
+ - customisable UI, through simple, pre-defined [themes](#themes), or specific CSS overrides
12
+ - self-contained — constructed with HTML/CSS, so no dependance on external UI libraries
13
+
14
+ **[Explore the Demo](https://carlosnz.github.io/json-edit-react/)**
15
+
16
+ <img width="392" alt="screenshot" src="image/screenshot.png">
17
+
18
+ - [Installation](#installation)
19
+ - [Implementation](#implementation)
20
+ - [Usage](#usage)
21
+ - [Props overview](#props-overview)
22
+ - [Update functions](#update-functions)
23
+ - [Copy function](#copy-function)
24
+ - [Filter functions](#filter-functions)
25
+ - [Examples](#examples)
26
+ - [Themes](#themes)
27
+ - [Fragments](#fragments)
28
+ - [A note about sizing and scaling](#a-note-about-sizing-and-scaling)
29
+ - [Icons](#icons)
30
+ - [Localisation](#localisation)
31
+ - [Undo functionality](#undo-functionality)
32
+ - [Issues, bugs, suggestions?](#issues-bugs-suggestions)
33
+ - [Roadmap](#roadmap)
34
+ - [Changelog](#changelog)
35
+
36
+
37
+ ## Installation
38
+
39
+ `npm i json-edit-react`
40
+
41
+ or
42
+
43
+ `yarn add json-edit-react`
44
+
45
+ ## Implementation
46
+
47
+ ```js
48
+ import { JsonEditor } from 'json-edit-react'
49
+
50
+ // In your React components:
51
+ <JsonEditor data={ myDataObject } { ...props }>
52
+
53
+ ```
54
+
55
+ ## Usage
56
+
57
+ **(for end user)**
58
+
59
+ 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:
60
+
61
+ - Double-click a value to edit it
62
+ - When editing a string, use `Cmd/Ctrl/Shift-Enter` to add a new line (`Enter` submits the value)
63
+ - 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
64
+ - `Escape` to cancel editing
65
+ - When clicking the "clipboard" icon, holding down `Cmd/Ctrl` will copy the *path* to the selected node rather than its value
66
+
67
+ ## Props overview
68
+
69
+ The only *required* value is `data`.
70
+
71
+ | prop | type | default | description |
72
+ | ------------------ | -------------------------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
73
+ | `data` | `object\|array` | | The data to be displayed / edited |
74
+ | `rootName` | `string` | `"data"` | A name to display in the editor as the root of the data object. |
75
+ | `onUpdate` | `UpdateFunction` | | A function to run whenever a value is **updated** (edit, delete *or* add) in the editor. See [Update functions](#update-functions). |
76
+ | `onUpdate` | `UpdateFunction` | | A function to run whenever a value is **edited**. |
77
+ | `onDelete` | `UpdateFunction` | | A function to run whenever a value is **deleted**. |
78
+ | `onAdd` | `UpdateFunction` | | A function to run whenever a new property is **added**. |
79
+ | `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. |
80
+ | `indent` | `number` | `4` | Specify the amount of indentation for each level of nesting in the displayed data. |
81
+ | `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). |
82
+ | `restrictEdit` | `boolean\|FilterFunction` | `false` | If `false`, no editing is permitted. A function can be provided for more specificity — see [Filter functions](#filter-functions) |
83
+ | `restrictDelete` | `boolean\|FilterFunction` | `false` | As with `restrictEdit` but for deletion |
84
+ | `restrictAdd` | `boolean\|FilterFunction` | `false` | As with `restrictEdit` but for adding new properties |
85
+ | `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. |
86
+ | `showArrayIndices` | `boolean` | `true` | Whether or not to display the index (as a property key) for array elements. |
87
+ | `defaultValue` | `any` | `null` | When a new property is added, it is initialised with this value. |
88
+ | `stringTruncate` | `number` | `250` | String values longer than this many characters will be displayed truncated (with `...`). The full string will always be visible when editing. |
89
+ | `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) |
90
+ | `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). |
91
+ | `className` | `string` | | Name of a CSS class to apply to the component. In most cases, specifying `theme` properties will be more straightforward. |
92
+ | `icons` | `{[iconName]: JSX.Element, ... }` | `{ }` | Replace the built-in icons by specifying them here. See [Themes](#themes). | |
93
+ | `minWidth` | `number\|string` (CSS value) | `250` | Minimum width for the editor container. |
94
+ | `maxWidth` | `number\|string` (CSS value) | `600` | Maximum width for the editor container. |
95
+
96
+ ## Update functions
97
+
98
+ 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 state, or make an API call, for example. 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.
99
+
100
+ The function will receive the following object as a parameter:
101
+
102
+ ```js
103
+ {
104
+ newData, // data state after update
105
+ currentData, // data state before update
106
+ newValue, // the new value of the property being updated
107
+ currentValue, // the current value of the property being updated
108
+ name, // name of the property being updated
109
+ path // full path to the property being updated, as an array of property keys
110
+ // (e.g. [ "user", "friends", 1, "name" ] ) (equivalent to "user.friends[1].name")
111
+ }
112
+ ```
113
+
114
+ The function needn't return anything, but if it returns `false`, it will be considered an error, in which case an error message will displayed in the UI and the internal data state won't actually be updated. If the return value is a `string`, this will be the error message displayed (i.e. you can define your own error messages for updates).
115
+
116
+ ### Copy function
117
+
118
+ 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:
119
+
120
+ ```js
121
+ key: // name of the property being copied
122
+ path: // path to the property
123
+ value: // the value copied to the clipboard (Note: this value will be serialised to a string)
124
+ type: // Either "path" or "value" depending on whether "Cmd/Ctrl" was pressed
125
+ ```
126
+
127
+ Since there is very little user feedback when clicking "Copy", a good idea would be to present some kind of notification in this callback.
128
+
129
+ ## Filter functions
130
+
131
+ You can control which nodes of the data structure can be edited, deleted, or added to 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).
132
+
133
+ The function receives the following object:
134
+ ```js
135
+ {
136
+ key, // name of the property
137
+ path, // path to the property (as an array of property keys)
138
+ level, // depth of the property (with 0 being the root)
139
+ value, // value of the property
140
+ size // if a collection (object, array), the number of items (null for non-collections)
141
+ }
142
+ ```
143
+
144
+ 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.
145
+
146
+ ### Examples
147
+
148
+ - A good case would be ensure your root node is not directly editable:
149
+
150
+ ```js
151
+ // in <JsonEditor /> props
152
+ restrictEdit = { ({ level }) => level === 0 }
153
+ ```
154
+
155
+ - Don't let the `id` field be edited:
156
+
157
+ ```js
158
+ restrictEdit = { ({ key }) => key === "id" }
159
+ // You'd probably want to include this in `restrictDelete` as well
160
+ ```
161
+
162
+ - Only individual properties can be deleted, not objects or arrays:
163
+
164
+ ```js
165
+ restrictDelete = { ({ size }) => size !== null }
166
+ ```
167
+
168
+ - The only collections that can have new items added are the "address" object and the "users" array:
169
+ ```js
170
+ restrictAdd = { ({ key }) => key !== "address" && key !== "users" }
171
+ // "Adding" is irrelevant for non-collection nodes
172
+ ```
173
+
174
+ ## Themes
175
+
176
+ 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 pass the name into the `theme` prop (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). The available themes are:
177
+ - `default`
178
+ - `githubDark`
179
+ - `githubLight`
180
+ - `monoDark`
181
+ - `monoLight`
182
+ - `candyWrapper`
183
+ - `psychedelic`
184
+
185
+ However, you can pass in your own theme object, or part thereof. The theme structure is as follows (this is the "default" theme definition):
186
+
187
+ ```js
188
+ {
189
+ displayName: 'Default',
190
+ fragments: { edit: 'rgb(42, 161, 152)' },
191
+ styles: {
192
+ container: {
193
+ backgroundColor: '#f6f6f6',
194
+ fontFamily: 'monospace',
195
+ },
196
+ property: '#292929',
197
+ bracket: { color: 'rgb(0, 43, 54)', fontWeight: 'bold' },
198
+ itemCount: { color: 'rgba(0, 0, 0, 0.3)', fontStyle: 'italic' },
199
+ string: 'rgb(203, 75, 22)',
200
+ number: 'rgb(38, 139, 210)',
201
+ boolean: 'green',
202
+ null: { color: 'rgb(220, 50, 47)', fontVariant: 'small-caps', fontWeight: 'bold' },
203
+ input: ['#292929', { fontSize: '90%' }],
204
+ inputHighlight: '#b3d8ff',
205
+ error: { fontSize: '0.8em', color: 'red', fontWeight: 'bold' },
206
+ iconCollection: 'rgb(0, 43, 54)',
207
+ iconEdit: 'edit',
208
+ iconDelete: 'rgb(203, 75, 22)',
209
+ iconAdd: 'edit',
210
+ iconCopy: 'rgb(38, 139, 210)',
211
+ iconOk: 'green',
212
+ iconCancel: 'rgb(203, 75, 22)',
213
+ },
214
+ }
215
+
216
+ ```
217
+
218
+ 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* a `string`, in which case it is interpreted as the colour (or background colour in the case of `container` and `inputHighlight`), *or* 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".
219
+
220
+ For example, if you want to use the "githubDark" theme, but just change a couple of small things, you'd specify something like this:
221
+
222
+ ```js
223
+ // in <JsonEditor /> props
224
+ theme={[
225
+ 'githubDark',
226
+ {
227
+ iconEdit: 'grey',
228
+ boolean: { color: 'red', fontStyle: 'italic', fontWeight: 'bold', fontSize: '80%' },
229
+ },
230
+ ]}
231
+ ```
232
+
233
+ Which would change the "Edit" icon and boolean values from this:
234
+ <img width="218" alt="Github Dark theme original" src="image/theme_edit_before.png">
235
+ into this:
236
+ <img width="218" alt="Github Dark theme modified" src="image/theme_edit_after.png">
237
+
238
+ Or you could create your own theme from scratch and overwrite the whole theme object.
239
+
240
+ So, to summarise, the `theme` prop can take *either*:
241
+
242
+ - a theme name e.g. `"candyWrapper"`
243
+ - a theme object:
244
+ - can be structured as above with `fragments`, `styles`, `displayName` etc., or just the `styles` part (at the root level)
245
+ - a theme name *and* an override object in an array, i.e. `[ "<themeName>, {...overrides } ]`
246
+
247
+ 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.
248
+
249
+ ### Fragments
250
+
251
+ 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:
252
+ ```js
253
+ fragments: { iconAdjust: { color: "blue", fontSize: "110%", marginRight: "0.6em" }}
254
+ ```
255
+
256
+ Then in the theme object, just use:
257
+ ```js
258
+ {
259
+ ...,
260
+ iconEdit: "iconAdjust",
261
+ iconDelete: "iconAdjust",
262
+ iconAdd: "iconAdjust",
263
+ iconCopy: "iconAdjust",
264
+ }
265
+ ```
266
+
267
+ Then, when you want to tweak it later, you only need to update it in one place!
268
+
269
+ Fragments can also be mixed with additional properties, and even other fragments, like so:
270
+ ```js
271
+ iconEdit: [ "iconAdjust", "anotherFragment", { marginLeft: "1em" } ]
272
+ ```
273
+
274
+ ### A note about sizing and scaling
275
+
276
+ Internally, all sizing and spacing is done in `em`s, never `px`. This makes scaling a lot easier — just change the font-size of the main container (either via the `className` prop or in the `container` prop of the theme) (or its parent), and watch the *whole* component scale accordingly.
277
+
278
+ ### Icons
279
+
280
+ 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:
281
+
282
+ ```js
283
+ icons={{
284
+ add: <YourIcon />
285
+ edit: <YourIcon />
286
+ delete: <YourIcon />
287
+ copy: <YourIcon />
288
+ ok: <YourIcon />
289
+ cancel: <YourIcon />
290
+ chevron: <YourIcon />
291
+ }}
292
+ ```
293
+
294
+ The Icon components will need to have their own styles defined, as the theme styles *won't* be added to the custom elements.
295
+
296
+ ## Localisation
297
+
298
+ Localise your implementation by passing in a `translations` object to replace the default strings. The keys and default (English) values are as follows:
299
+ ```js
300
+ {
301
+ ITEM_SINGLE: '{{count}} item',
302
+ ITEMS_MULTIPLE: '{{count}} items',
303
+ KEY_NEW: 'Enter new key',
304
+ ERROR_KEY_EXISTS: 'Key already exists',
305
+ ERROR_INVALID_JSON: 'Invalid JSON',
306
+ ERROR_UPDATE: 'Update unsuccessful',
307
+ ERROR_DELETE: 'Delete unsuccessful',
308
+ ERROR_ADD: 'Adding node unsuccessful',
309
+ }
310
+
311
+ ```
312
+
313
+ ## Undo functionality
314
+
315
+ Even though Undo/Redo functionality is probably desirable in most cases, this is not built in to the component, for two main reasons:
316
+ 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)
317
+ 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.
318
+
319
+ ## Issues, bugs, suggestions?
320
+
321
+ Please open an issue: https://github.com/CarlosNZ/json-edit-react/issues
322
+
323
+ ## Roadmap
324
+
325
+ The main features I'd like to introduce are:
326
+
327
+ 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.
328
+ 2. **Visibility filter function** — *hide* properties from the UI completely based on a Filter function. This should arguably be done outside the component though (filter the data upstream), so would be less of a priority (though it would be fairly simple to implement, so 🤷‍♂️)
329
+ 3. **Search** — 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?
330
+
331
+
332
+ ## Changelog
333
+
334
+ - **0.9.0**: Initial release