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.
@@ -0,0 +1,873 @@
1
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
+ import { createContext, useState, useContext, useEffect, useMemo, useCallback } from 'react';
3
+ import assign from 'object-property-assigner';
4
+ import extract from 'object-property-extractor';
5
+ import clone from 'just-clone';
6
+ import { HiOutlineClipboardCopy } from 'react-icons/hi';
7
+ import { BiEdit, BiPlusCircle } from 'react-icons/bi';
8
+ import { MdDeleteForever } from 'react-icons/md';
9
+ import { FiCheckCircle } from 'react-icons/fi';
10
+ import { TiCancel } from 'react-icons/ti';
11
+ import { FaChevronDown } from 'react-icons/fa';
12
+
13
+ var e=[],t=[];function n(n,r){if(n&&"undefined"!=typeof document){var a,s=!0===r.prepend?"prepend":"append",d=!0===r.singleTag,i="string"==typeof r.container?document.querySelector(r.container):document.getElementsByTagName("head")[0];if(d){var u=e.indexOf(i);-1===u&&(u=e.push(i)-1,t[u]={}),a=t[u]&&t[u][s]?t[u][s]:t[u][s]=c();}else a=c();65279===n.charCodeAt(0)&&(n=n.substring(1)),a.styleSheet?a.styleSheet.cssText+=n:a.appendChild(document.createTextNode(n));}function c(){var e=document.createElement("style");if(e.setAttribute("type","text/css"),r.attributes)for(var t=Object.keys(r.attributes),n=0;n<t.length;n++)e.setAttribute(t[n],r.attributes[t[n]]);var a="prepend"===s?"afterbegin":"beforeend";return i.insertAdjacentElement(a,e),e}}
14
+
15
+ var css = ":root {\n --jer-form-border: 1px solid #ededf0;\n --jer-form-border-focus: 1px solid #e2e2e2;\n --jer-expand-transition-time: 0.2s;\n --jer-highlight-color: #b3d8ff;\n}\n.jer-editor-container {\n font-size: 1.1em;\n /* font-size: 20px; */\n padding: 1em;\n margin-top: 1em;\n margin-bottom: 1em;\n border-radius: 0.5em;\n}\n\n.jer-editor-container textarea {\n /* width: 90%; */\n color: var(--jer-input-color);\n border: var(--jer-form-border);\n border-radius: 0.3em;\n outline: none;\n}\n\n.jer-editor-container textarea:focus {\n border: var(--jer-form-border-focus);\n}\n\n.jer-editor-container input {\n border: var(--jer-form-border);\n border-radius: 0.3em;\n outline: none;\n}\n\n.jer-editor-container input:focus {\n border: var(--jer-form-border-focus);\n}\n\n/* Input highlighted text */\n.jer-editor-container ::selection {\n background-color: var(--jer-highlight-color);\n}\n\n.jer-editor-container select {\n /* color: red; */\n}\n\n.jer-component {\n}\n\n.fb-collection-component {\n}\n\n.jer-collection-header-row,\n.jer-value-main-row {\n display: flex;\n min-height: 1.7em;\n gap: 0.3em;\n align-items: center;\n}\n\n.jer-collection-header-row {\n display: flex;\n flex-wrap: wrap;\n}\n\n.jer-brackets {\n}\n\n.jer-collection-item-count {\n}\n\n.jer-collection-inner {\n position: relative;\n}\n\n.jer-collection-text-edit {\n display: flex;\n flex-direction: column;\n gap: 0.3em;\n}\n\n.jer-collection-text-area {\n resize: both;\n padding-top: 0.2em;\n padding-left: 0.5em;\n padding-right: 0.5em;\n padding-bottom: 0;\n overflow: hidden;\n max-height: 40em;\n}\n\n.jer-collection-input-button-row {\n width: 90%;\n display: flex;\n justify-content: flex-end;\n font-size: 150%;\n}\n\n.jer-collection-element {\n}\n\n.jer-collection-error-row {\n position: absolute;\n bottom: 0;\n}\n\n.jer-collection-error-row-edit {\n}\n\n.jer-error-slug {\n margin-left: 1em;\n}\n\n.jer-value-component {\n position: relative;\n}\n\n.jer-value-main-row {\n display: flex;\n}\n\n.jer-value-error-row {\n position: absolute;\n}\n\n.jer-value-string {\n line-height: 1.3em;\n}\n\n.jer-input-text {\n resize: none;\n margin: 0;\n height: 1.4em;\n padding-left: 0.5em;\n padding-right: 0.5em;\n padding-bottom: 0;\n min-width: 6em;\n overflow: hidden;\n max-height: 30em;\n}\n\n/* To prevent over-writing by UI libraries */\n.jer-type-select {\n border: revert;\n border-radius: revert;\n padding: revert;\n font-family: revert;\n font-size: revert;\n color: revert;\n}\n\n.jer-value-number {\n}\n\n.jer-value-boolean {\n}\n\n.jer-value-null {\n}\n\n.jer-value-object {\n}\n\n.jer-object-key {\n word-break: break-word;\n line-height: 1.1em;\n}\n\n.jer-value-array {\n}\n\n.jer-value-invalid {\n opacity: 0.5;\n font-style: italic;\n}\n\n/* .jer-input-text, */\n.jer-input-number {\n height: 1.6em;\n}\n\n.jer-input-number {\n min-width: 3em;\n}\n\n.jer-input-component {\n}\n\n.jer-edit-buttons,\n.jer-confirm-buttons {\n display: flex;\n align-items: center;\n cursor: pointer;\n}\n\n.jer-input-buttons {\n gap: 0.4em;\n}\n\n.jer-edit-buttons {\n gap: 0.4em;\n margin-left: 0.5em;\n opacity: 0;\n}\n\n.jer-confirm-buttons {\n gap: 0.2em;\n margin-left: 0.4em;\n}\n\n.jer-edit-buttons:hover {\n opacity: 1;\n position: relative;\n}\n\n.jer-collection-header-row:hover > .jer-edit-buttons {\n opacity: 1;\n}\n\n.jer-value-main-row:hover > .jer-edit-buttons {\n opacity: 1;\n}\n\n.jer-input-buttons {\n}\n\n.jer-icon-wrapper {\n height: 1.4em;\n opacity: 1;\n transition: 0.2s;\n}\n\n.jer-icon-wrapper:active {\n position: relative;\n top: 0.07em;\n}\n\n.jer-icon-wrapper:hover {\n opacity: 0.85;\n transition: 0.2s;\n transform: scale(1.2);\n}\n\n.jer-copy-pulse {\n position: relative;\n}\n\n.jer-copy-pulse:hover {\n}\n\n.jer-copy-pulse:after {\n content: '';\n display: block;\n position: absolute;\n border-radius: 50%;\n left: 0;\n top: 0;\n width: 100%;\n height: 100%;\n opacity: 0;\n transition: all 0.5s;\n box-shadow: 0 0 15px 5px var(--jer-icon-copy-color);\n}\n\n.jer-copy-pulse:active:after {\n box-shadow: 0 0 0 0 var(--jer-icon-copy-color);\n position: absolute;\n border-radius: 4em;\n left: 0;\n top: 0;\n opacity: 1;\n transition: 0s;\n}\n\n.jer-copy-pulse:active {\n top: 0.07em;\n}\n\n.jer-rotate-90 {\n transform: rotate(-90deg);\n transition: transform var(--jer-expand-transition-time);\n}\n\n.jer-icon {\n /* font-size: '2em'; */\n}\n\n.jer-icon:hover {\n opacity: 0.85;\n transition: 0.3s;\n transform: scale(1.2);\n}\n";
16
+ n(css,{});
17
+
18
+ const defaultTheme$1 = {
19
+ displayName: 'Default',
20
+ fragments: { edit: 'rgb(42, 161, 152)' },
21
+ styles: {
22
+ container: {
23
+ backgroundColor: '#f6f6f6',
24
+ fontFamily: 'monospace',
25
+ },
26
+ property: '#292929',
27
+ bracket: { color: 'rgb(0, 43, 54)', fontWeight: 'bold' },
28
+ itemCount: { color: 'rgba(0, 0, 0, 0.3)', fontStyle: 'italic' },
29
+ string: 'rgb(203, 75, 22)',
30
+ number: 'rgb(38, 139, 210)',
31
+ boolean: 'green',
32
+ null: { color: 'rgb(220, 50, 47)', fontVariant: 'small-caps', fontWeight: 'bold' },
33
+ input: ['#292929', { fontSize: '90%' }],
34
+ inputHighlight: '#b3d8ff',
35
+ error: { fontSize: '0.8em', color: 'red', fontWeight: 'bold' },
36
+ iconCollection: 'rgb(0, 43, 54)',
37
+ iconEdit: 'edit',
38
+ iconDelete: 'rgb(203, 75, 22)',
39
+ iconAdd: 'edit',
40
+ iconCopy: 'rgb(38, 139, 210)',
41
+ iconOk: 'green',
42
+ iconCancel: 'rgb(203, 75, 22)',
43
+ },
44
+ };
45
+ const themes = {
46
+ default: defaultTheme$1,
47
+ githubDark: {
48
+ displayName: 'Github Dark',
49
+ styles: {
50
+ container: {
51
+ backgroundColor: '#0d1117',
52
+ color: 'white',
53
+ },
54
+ property: '#E6EDF3',
55
+ bracket: '#56d364',
56
+ itemCount: '#8B949E',
57
+ string: '#A5D6FF',
58
+ number: '#D2A8FF',
59
+ boolean: { color: '#FF7B72', fontSize: '90%', fontWeight: 'bold' },
60
+ null: 'green',
61
+ iconCollection: '#D2A8FF',
62
+ iconEdit: '#D2A8FF',
63
+ iconDelete: 'rgb(203, 75, 22)',
64
+ iconAdd: 'rgb(203, 75, 22)',
65
+ iconCopy: '#A5D6FF',
66
+ iconOk: '#56d364',
67
+ iconCancel: 'rgb(203, 75, 22)',
68
+ },
69
+ },
70
+ githubLight: {
71
+ displayName: 'Github Light',
72
+ styles: {
73
+ container: 'white',
74
+ property: '#1F2328',
75
+ bracket: '#00802e',
76
+ itemCount: '#8B949E',
77
+ string: '#0A3069',
78
+ number: '#953800',
79
+ boolean: { color: '#CF222E', fontSize: '90%', fontWeight: 'bold' },
80
+ null: '#FF7B72',
81
+ iconCollection: '#8250DF',
82
+ iconEdit: '#8250DF',
83
+ iconDelete: 'rgb(203, 75, 22)',
84
+ iconAdd: '#8250DF',
85
+ iconCopy: '#57606A',
86
+ },
87
+ },
88
+ monoDark: {
89
+ displayName: 'Black & White',
90
+ fragments: {
91
+ lightText: { color: 'white' },
92
+ midGrey: '#5c5c5c',
93
+ },
94
+ styles: {
95
+ container: ['lightText', { backgroundColor: 'black' }],
96
+ property: 'lightText',
97
+ bracket: 'midGrey',
98
+ itemCount: '#4a4a4a',
99
+ string: '#a8a8a8',
100
+ number: '#666666',
101
+ boolean: { color: '#848484', fontStyle: 'italic' },
102
+ null: '#333333',
103
+ iconCollection: 'midGrey',
104
+ iconEdit: 'midGrey',
105
+ iconDelete: 'midGrey',
106
+ iconAdd: 'midGrey',
107
+ iconCopy: 'midGrey',
108
+ iconOk: 'midGrey',
109
+ iconCancel: 'midGrey',
110
+ },
111
+ },
112
+ monoLight: {
113
+ fragments: { midGrey: '#a3a3a3' },
114
+ displayName: 'White & Black',
115
+ styles: {
116
+ container: 'white',
117
+ property: 'black',
118
+ bracket: 'midGrey',
119
+ itemCount: '#b5b5b5',
120
+ string: '#575757',
121
+ number: '#999999',
122
+ boolean: { color: '#7b7b7b', fontStyle: 'italic' },
123
+ null: '#cccccc',
124
+ iconCollection: 'midGrey',
125
+ iconEdit: 'midGrey',
126
+ iconDelete: 'midGrey',
127
+ iconAdd: 'midGrey',
128
+ iconCopy: 'midGrey',
129
+ iconOk: 'midGrey',
130
+ iconCancel: 'midGrey',
131
+ },
132
+ },
133
+ candyWrapper: {
134
+ displayName: 'Candy Wrapper',
135
+ fragments: {
136
+ minty: { backgroundColor: '#F1FAEE' },
137
+ pale: { color: '#A8DADC' },
138
+ mid: { color: '#457B9D' },
139
+ dark: { color: '#1D3557' },
140
+ pop: { color: '#E63946' },
141
+ darkBlue: { color: '#2B2D42' },
142
+ },
143
+ styles: {
144
+ container: 'minty',
145
+ property: 'pop',
146
+ bracket: 'dark',
147
+ itemCount: 'pale',
148
+ string: 'mid',
149
+ number: ['darkBlue', { fontSize: '85%' }],
150
+ boolean: ['mid', { fontStyle: 'italic', fontWeight: 'bold', fontSize: '80%' }],
151
+ null: ['#cccccc', { fontWeight: 'bold' }],
152
+ iconCollection: '#1D3557',
153
+ iconEdit: '#457B9D',
154
+ iconDelete: '#E63946',
155
+ iconAdd: '#2B2D42',
156
+ iconCopy: '#1D3557',
157
+ iconCancel: '#E63946',
158
+ },
159
+ },
160
+ psychedelic: {
161
+ displayName: 'Psychedelic',
162
+ fragments: {
163
+ minty: {
164
+ backgroundColor: '#F1FAEE',
165
+ },
166
+ pale: {
167
+ color: '#A8DADC',
168
+ },
169
+ mid: {
170
+ color: '#457B9D',
171
+ },
172
+ dark: {
173
+ color: '#1D3557',
174
+ },
175
+ pop: {
176
+ color: '#E63946',
177
+ },
178
+ fluroYellow: 'rgb(242, 228, 21)',
179
+ fluroGreen: 'rgb(68, 255, 62)',
180
+ hotPink: '#f7379a',
181
+ },
182
+ styles: {
183
+ container: {
184
+ backgroundColor: 'unset',
185
+ background: 'linear-gradient(90deg, hsla(333, 100%, 53%, 1) 0%, hsla(33, 94%, 57%, 1) 100%)',
186
+ color: 'black',
187
+ },
188
+ property: 'black',
189
+ bracket: 'fluroYellow',
190
+ itemCount: [
191
+ 'pale',
192
+ {
193
+ opacity: 0.7,
194
+ },
195
+ ],
196
+ string: 'white',
197
+ number: [
198
+ '#33d9ff',
199
+ {
200
+ fontSize: '90%',
201
+ fontWeight: 'bold',
202
+ },
203
+ ],
204
+ boolean: [
205
+ 'fluroGreen',
206
+ {
207
+ fontWeight: 'bold',
208
+ fontSize: '80%',
209
+ },
210
+ ],
211
+ null: [
212
+ 'black',
213
+ {
214
+ fontWeight: 'bold',
215
+ opacity: 0.3,
216
+ backgroundColor: 'rgb(255, 255, 255, 0.5)',
217
+ padding: '0 0.4em',
218
+ borderRadius: '0.4em',
219
+ },
220
+ ],
221
+ iconCollection: 'fluroYellow',
222
+ iconEdit: ['black'],
223
+ iconDelete: [
224
+ 'white',
225
+ {
226
+ opacity: 0.5,
227
+ },
228
+ ],
229
+ iconAdd: [
230
+ 'white',
231
+ {
232
+ opacity: 0.5,
233
+ },
234
+ ],
235
+ iconCopy: 'rgb(32, 84, 242)',
236
+ iconOk: 'fluroGreen',
237
+ iconCancel: 'hotPink',
238
+ },
239
+ },
240
+ };
241
+ const emptyStyleObject = {
242
+ container: {},
243
+ property: {},
244
+ bracket: {},
245
+ itemCount: {},
246
+ string: {},
247
+ number: {},
248
+ boolean: {},
249
+ null: {},
250
+ input: {},
251
+ inputHighlight: {},
252
+ error: {},
253
+ iconCollection: {},
254
+ iconEdit: {},
255
+ iconDelete: {},
256
+ iconAdd: {},
257
+ iconCopy: {},
258
+ iconOk: {},
259
+ iconCancel: {},
260
+ };
261
+
262
+ const defaultTheme = themes.default;
263
+ const initialContext = {
264
+ styles: emptyStyleObject,
265
+ setTheme: (_) => { },
266
+ icons: {},
267
+ setIcons: () => { },
268
+ };
269
+ const ThemeProviderContext = createContext(initialContext);
270
+ const ThemeProvider = ({ children }) => {
271
+ const [styles, setStyles] = useState(emptyStyleObject);
272
+ const [icons, setIcons] = useState({});
273
+ const setTheme = (theme) => setStyles(compileStyles(theme));
274
+ return (jsx(ThemeProviderContext.Provider, { value: { styles, setTheme, icons, setIcons }, children: children }));
275
+ };
276
+ const useTheme = () => useContext(ThemeProviderContext);
277
+ const compileStyles = (themeInput) => {
278
+ if (typeof themeInput === 'string')
279
+ return buildStyleObject(themes[themeInput]);
280
+ if (Array.isArray(themeInput)) {
281
+ const [name, overrides] = themeInput;
282
+ return buildStyleObject(themes[name], isStyleObject(overrides) ? { styles: overrides } : overrides);
283
+ }
284
+ return buildStyleObject(defaultTheme, isStyleObject(themeInput) ? { styles: themeInput } : themeInput);
285
+ };
286
+ const buildStyleObject = (baseTheme, overrides = { styles: emptyStyleObject }) => {
287
+ const [defaultStyles, baseStyles, overrideStyles] = [defaultTheme, baseTheme, overrides].map((theme) => {
288
+ const { fragments, styles } = theme;
289
+ const compiledStyles = {};
290
+ Object.entries(styles).forEach(([key, value]) => {
291
+ const elements = Array.isArray(value) ? value : [value];
292
+ const cssStyles = elements.reduce((acc, curr) => {
293
+ if (typeof curr === 'string') {
294
+ const style = fragments?.[curr] ?? curr;
295
+ switch (typeof style) {
296
+ case 'string':
297
+ return { ...acc, [defaultStyleProperties[key]]: style };
298
+ default:
299
+ return { ...acc, ...style };
300
+ }
301
+ }
302
+ else
303
+ return { ...acc, ...curr };
304
+ }, {});
305
+ compiledStyles[key] = cssStyles;
306
+ });
307
+ return compiledStyles;
308
+ });
309
+ const finalStyles = {};
310
+ Object.keys(defaultTheme.styles).forEach((key) => {
311
+ finalStyles[key] = {
312
+ ...defaultStyles[key],
313
+ ...baseStyles[key],
314
+ ...overrideStyles[key],
315
+ };
316
+ });
317
+ if (finalStyles?.inputHighlight?.backgroundColor)
318
+ document.documentElement.style.setProperty('--jer-highlight-color', finalStyles?.inputHighlight?.backgroundColor);
319
+ if (finalStyles?.iconCopy?.color)
320
+ document.documentElement.style.setProperty('--jer-icon-copy-color', finalStyles?.iconCopy?.color);
321
+ return finalStyles;
322
+ };
323
+ const isStyleObject = (overrideObject) => {
324
+ return !('styles' in overrideObject);
325
+ };
326
+ const defaultStyleProperties = {
327
+ container: 'backgroundColor',
328
+ property: 'color',
329
+ bracket: 'color',
330
+ itemCount: 'color',
331
+ string: 'color',
332
+ number: 'color',
333
+ boolean: 'color',
334
+ null: 'color',
335
+ input: 'color',
336
+ inputHighlight: 'backgroundColor',
337
+ error: 'color',
338
+ iconCollection: 'color',
339
+ iconEdit: 'color',
340
+ iconDelete: 'color',
341
+ iconAdd: 'color',
342
+ iconCopy: 'color',
343
+ iconOk: 'color',
344
+ iconCancel: 'color',
345
+ };
346
+
347
+ const AutogrowTextArea = ({ className, name, value, setValue, handleKeyPress, }) => {
348
+ const { styles } = useTheme();
349
+ const dummyValue = value.slice(-1) === '\n' ? value + '.' : value;
350
+ return (jsxs("div", { style: { display: 'grid' }, children: [jsx("textarea", { style: {
351
+ height: 'auto',
352
+ gridArea: '1 / 1 / 2 / 2',
353
+ overflowY: 'auto',
354
+ ...styles.input,
355
+ }, rows: 1, className: className, name: name, value: value, onChange: (e) => setValue(e.target.value), autoFocus: true, onFocus: (e) => {
356
+ if (value.length < 40)
357
+ e.target.select();
358
+ }, onKeyDown: handleKeyPress }), jsx("span", { className: className, style: {
359
+ visibility: 'hidden',
360
+ height: 'auto',
361
+ gridArea: '1 / 1 / 2 / 2',
362
+ color: 'red',
363
+ opacity: 0.9,
364
+ whiteSpace: 'pre-wrap',
365
+ overflow: 'clip',
366
+ border: '1px solid transparent',
367
+ ...styles.input,
368
+ }, children: dummyValue })] }));
369
+ };
370
+
371
+ const truncate = (string, length = 200) => string.length < length ? string : `${string.slice(0, length - 2).trim()}...`;
372
+ const StringValue = ({ value, setValue, isEditing, path, setIsEditing, handleEdit, handleCancel, stringTruncate, }) => {
373
+ const { styles } = useTheme();
374
+ const handleKeyPress = (e) => {
375
+ if (e.key === 'Enter' && !e.shiftKey)
376
+ handleEdit();
377
+ else if (e.key === 'Escape')
378
+ handleCancel();
379
+ };
380
+ const breakString = (text) => text.split('\n').map((line, index, arr) => (jsxs("span", { children: [line, index < arr.length - 1 ? jsx("br", {}) : null] }, index)));
381
+ return isEditing ? (jsx(AutogrowTextArea, { className: "jer-input-text", name: path.join('.'), value: value, setValue: setValue, isEditing: isEditing, handleKeyPress: handleKeyPress })) : (jsxs("div", { onDoubleClick: () => setIsEditing(true), onClick: (e) => {
382
+ if (e.getModifierState('Control') || e.getModifierState('Meta'))
383
+ setIsEditing(true);
384
+ }, className: "jer-value-string", style: styles.string, children: ["\"", breakString(truncate(value, stringTruncate)), "\""] }));
385
+ };
386
+ const NumberValue = ({ value, setValue, isEditing, path, setIsEditing, handleEdit, handleCancel, }) => {
387
+ const { styles } = useTheme();
388
+ const handleKeyPress = (e) => {
389
+ if (e.key === 'Enter')
390
+ handleEdit();
391
+ else if (e.key === 'Escape')
392
+ handleCancel();
393
+ };
394
+ const validateNumber = (input) => {
395
+ return input.replace(/[^0-9.-]/g, '');
396
+ };
397
+ return isEditing ? (jsx("input", { className: "jer-input-number", type: "text", name: path.join('.'), value: value, onChange: (e) => setValue(validateNumber(e.target.value)), autoFocus: true, onFocus: (e) => e.target.select(), onKeyDown: handleKeyPress, style: { width: `${String(value).length / 1.5 + 2}em` } })) : (jsx("span", { onDoubleClick: () => setIsEditing(true), className: "jer-value-number", style: styles.number, children: value }));
398
+ };
399
+ const BooleanValue = ({ value, setValue, isEditing, path, setIsEditing, }) => {
400
+ const { styles } = useTheme();
401
+ return isEditing ? (jsx("input", { className: "jer-input-boolean", type: "checkbox", name: path.join('.'), checked: value, onChange: () => setValue(!value) })) : (jsx("span", { onDoubleClick: () => setIsEditing(true), className: "jer-value-boolean", style: styles.boolean, children: String(value) }));
402
+ };
403
+ const NullValue = ({ value, isEditing, setIsEditing }) => {
404
+ const { styles } = useTheme();
405
+ return isEditing ? (jsx("div", { className: "jer-input-null", children: "null" })) : (jsx("div", { onDoubleClick: () => setIsEditing(true), className: "jer-value-null", style: styles.null, children: String(value) }));
406
+ };
407
+ const ObjectValue = () => {
408
+ return jsx("span", { className: "jer-value-object", children: '{ }' });
409
+ };
410
+ const ArrayValue = ({ value }) => {
411
+ return jsx("span", { className: "jer-value-array", children: `[${value === null ? '' : value}]` });
412
+ };
413
+ const InvalidValue = () => {
414
+ return jsx("span", { className: "jer-value-invalid", children: "Invalid value" });
415
+ };
416
+
417
+ const Icon = ({ name, rotate }) => {
418
+ const { styles, icons } = useTheme();
419
+ const commonProps = { size: '1.4em', className: 'jer-icon' };
420
+ switch (name) {
421
+ case 'add':
422
+ return icons?.add ?? jsx(BiPlusCircle, { ...commonProps, style: styles.iconAdd });
423
+ case 'edit':
424
+ return icons?.edit ?? jsx(BiEdit, { ...commonProps, style: styles.iconEdit });
425
+ case 'delete':
426
+ return (icons?.delete ?? jsx(MdDeleteForever, { ...commonProps, style: styles.iconDelete, size: "1.5em" }));
427
+ case 'copy':
428
+ return icons?.copy ?? jsx(HiOutlineClipboardCopy, { ...commonProps, style: styles.iconCopy });
429
+ case 'ok':
430
+ return (icons?.ok ?? (jsx(FiCheckCircle, { ...commonProps, style: { fontSize: '90%', ...styles.iconOk } })));
431
+ case 'cancel':
432
+ return (icons?.cancel ?? (jsx(TiCancel, { ...commonProps, style: { fontSize: '130%', ...styles.iconCancel } })));
433
+ case 'chevron':
434
+ return (icons?.chevron ?? (jsx(FaChevronDown, { className: `${rotate ? ' jer-rotate-90' : ''}`, style: styles.iconCollection })));
435
+ default:
436
+ return jsx(Fragment, {});
437
+ }
438
+ };
439
+
440
+ const EditButtons = ({ startEdit, handleDelete, handleAdd, enableClipboard, type, data, path, name, translate, }) => {
441
+ const { styles } = useTheme();
442
+ const NEW_KEY_PROMPT = translate('KEY_NEW');
443
+ const [isAdding, setIsAdding] = useState(false);
444
+ const [newKey, setNewKey] = useState(NEW_KEY_PROMPT);
445
+ useEffect(() => {
446
+ if (!isAdding)
447
+ setNewKey(NEW_KEY_PROMPT);
448
+ }, [isAdding]);
449
+ const handleKeyPress = (e) => {
450
+ if (e.key === 'Enter' && handleAdd) {
451
+ setIsAdding(false);
452
+ handleAdd(newKey);
453
+ }
454
+ else if (e.key === 'Escape')
455
+ setIsAdding(false);
456
+ };
457
+ const handleCopy = (e) => {
458
+ let type = 'value';
459
+ let value;
460
+ if (enableClipboard) {
461
+ switch (e.ctrlKey || e.metaKey) {
462
+ case true:
463
+ value = stringifyPath(path);
464
+ type = 'path';
465
+ break;
466
+ case false:
467
+ value = JSON.stringify(data, null, 2);
468
+ }
469
+ navigator.clipboard.writeText(value);
470
+ }
471
+ if (typeof enableClipboard === 'function')
472
+ enableClipboard({ value, path, key: name, type });
473
+ };
474
+ return (jsxs("div", { className: "jer-edit-buttons", style: isAdding ? { opacity: 1 } : undefined, children: [enableClipboard && (jsx("div", { onClick: handleCopy, className: "jer-copy-pulse", children: jsx(Icon, { name: "copy" }) })), startEdit && (jsx("div", { onClick: startEdit, children: jsx(Icon, { name: "edit" }) })), handleDelete && (jsx("div", { onClick: handleDelete, children: jsx(Icon, { name: "delete" }) })), handleAdd && (jsx("div", { onClick: () => {
475
+ if (type === 'object')
476
+ setIsAdding(true);
477
+ else
478
+ handleAdd('');
479
+ }, children: jsx(Icon, { name: "add" }) })), isAdding && handleAdd && type === 'object' && (jsxs(Fragment, { children: [jsx("input", { className: "jer-input-new-key", type: "text", name: "new-object-key", value: newKey, onChange: (e) => setNewKey(e.target.value), autoFocus: true, onFocus: (e) => e.target.select(), onKeyDown: handleKeyPress, style: { ...styles.input } }), jsx(InputButtons, { onOk: () => {
480
+ if (!!newKey) {
481
+ setIsAdding(false);
482
+ handleAdd(newKey);
483
+ }
484
+ }, onCancel: () => {
485
+ setIsAdding(false);
486
+ } })] }))] }));
487
+ };
488
+ const InputButtons = ({ onOk, onCancel }) => {
489
+ return (jsxs("div", { className: "jer-confirm-buttons", children: [jsx("div", { onClick: onOk, children: jsx(Icon, { name: "ok" }) }), jsx("div", { onClick: onCancel, children: jsx(Icon, { name: "cancel" }) })] }));
490
+ };
491
+ const stringifyPath = (path) => path.reduce((str, part) => {
492
+ if (typeof part === 'number')
493
+ return `${str}[${part}]`;
494
+ else
495
+ return str === '' ? part : `${str}.${part}`;
496
+ }, '');
497
+
498
+ const ERROR_DISPLAY_TIME = 2500;
499
+ const ValueDataTypes = ['string', 'number', 'boolean', 'null'];
500
+ const CollectionDataTypes = ['object', 'array'];
501
+ const DataTypes = [...ValueDataTypes, ...CollectionDataTypes];
502
+
503
+ const ValueNodeWrapper = ({ data, name, path, onEdit, onDelete, enableClipboard, restrictEditFilter, restrictDeleteFilter, showArrayIndices, stringTruncate, indent, translate, }) => {
504
+ const { styles } = useTheme();
505
+ const [isEditing, setIsEditing] = useState(false);
506
+ const [value, setValue] = useState(data);
507
+ const [error, setError] = useState(null);
508
+ const [dataType, setDataType] = useState(getDataType(data));
509
+ useEffect(() => {
510
+ setValue(data);
511
+ setDataType(getDataType(data));
512
+ }, [data, error]);
513
+ const handleChangeDataType = (type) => {
514
+ setValue(convertValue(value, type));
515
+ setDataType(type);
516
+ };
517
+ const logError = (errorString) => {
518
+ setError(errorString);
519
+ setTimeout(() => setError(null), ERROR_DISPLAY_TIME);
520
+ console.log('Error', errorString);
521
+ };
522
+ const handleEdit = () => {
523
+ setIsEditing(false);
524
+ let newValue;
525
+ switch (dataType) {
526
+ case 'object':
527
+ newValue = {};
528
+ break;
529
+ case 'array':
530
+ newValue = value !== null ? [value] : [];
531
+ break;
532
+ case 'number':
533
+ const n = Number(value);
534
+ if (isNaN(n))
535
+ newValue = 0;
536
+ else
537
+ newValue = n;
538
+ break;
539
+ default:
540
+ newValue = value;
541
+ }
542
+ onEdit(newValue, path).then((error) => {
543
+ if (error)
544
+ logError(error);
545
+ });
546
+ };
547
+ const handleCancel = () => {
548
+ setIsEditing(false);
549
+ setValue(data);
550
+ };
551
+ const handleDelete = () => {
552
+ onDelete(value, path).then((error) => {
553
+ if (error)
554
+ logError(error);
555
+ });
556
+ };
557
+ const filterProps = { key: name, path, level: path.length, value: data, size: null };
558
+ const canEdit = useMemo(() => !restrictEditFilter(filterProps), [filterProps]);
559
+ const canDelete = useMemo(() => !restrictDeleteFilter(filterProps), [filterProps]);
560
+ const inputProps = {
561
+ value,
562
+ setValue,
563
+ isEditing,
564
+ setIsEditing,
565
+ handleEdit,
566
+ handleCancel,
567
+ path,
568
+ stringTruncate,
569
+ };
570
+ return (jsx("div", { className: "jer-component jer-value-component", style: { marginLeft: `${indent / 2}em` }, children: jsxs("div", { className: "jer-value-main-row", children: [showArrayIndices && (jsxs("label", { htmlFor: path.join('.'), className: "jer-object-key", style: {
571
+ ...styles.property,
572
+ minWidth: `${Math.min(String(name).length + 1, 5)}ch`,
573
+ }, children: [name, ":", ' '] })), jsx("div", { className: "jer-input-component", children: getInputComponent(dataType, inputProps) }), isEditing ? (jsx(InputButtons, { onOk: handleEdit, onCancel: handleCancel })) : (dataType !== 'invalid' &&
574
+ !error && (jsx(EditButtons, { startEdit: canEdit ? () => setIsEditing(true) : undefined, handleDelete: canDelete ? handleDelete : undefined, data: data, enableClipboard: enableClipboard, name: name, path: path, translate: translate }))), isEditing && (jsx("select", { name: `${name}-type-select`, className: "jer-type-select", onChange: (e) => handleChangeDataType(e.target.value), value: dataType, children: DataTypes.map((type) => (jsx("option", { value: type, children: type }, type))) })), !isEditing && error && (jsx("span", { className: "jer-error-slug", style: styles.error, children: error }))] }) }));
575
+ };
576
+ const getDataType = (value) => {
577
+ if (typeof value === 'string')
578
+ return 'string';
579
+ if (typeof value === 'number')
580
+ return 'number';
581
+ if (typeof value === 'boolean')
582
+ return 'boolean';
583
+ if (value === null)
584
+ return 'null';
585
+ return 'invalid';
586
+ };
587
+ const getInputComponent = (dataType, inputProps) => {
588
+ const value = inputProps.value;
589
+ switch (dataType) {
590
+ case 'string':
591
+ return jsx(StringValue, { ...inputProps, value: value });
592
+ case 'number':
593
+ return jsx(NumberValue, { ...inputProps, value: value });
594
+ case 'boolean':
595
+ return jsx(BooleanValue, { ...inputProps, value: value });
596
+ case 'null':
597
+ return jsx(NullValue, { ...inputProps });
598
+ case 'object':
599
+ return jsx(ObjectValue, { ...inputProps });
600
+ case 'array':
601
+ return jsx(ArrayValue, { ...inputProps });
602
+ default:
603
+ return jsx(InvalidValue, { ...inputProps });
604
+ }
605
+ };
606
+ const convertValue = (value, type) => {
607
+ switch (type) {
608
+ case 'string':
609
+ return String(value);
610
+ case 'number':
611
+ const n = Number(value);
612
+ return isNaN(n) ? 0 : n;
613
+ case 'boolean':
614
+ return !!value;
615
+ case 'null':
616
+ return null;
617
+ case 'object':
618
+ return {};
619
+ case 'array':
620
+ return [value];
621
+ default:
622
+ return String(value);
623
+ }
624
+ };
625
+
626
+ const isCollection = (value) => value !== null && typeof value == 'object';
627
+ const CollectionNode = ({ data, path, name, ...props }) => {
628
+ const { styles } = useTheme();
629
+ const { onEdit, onAdd, onDelete, restrictEditFilter, restrictDeleteFilter, restrictAddFilter, collapseFilter, enableClipboard, indent, keySort, showArrayIndices, defaultValue, translate, } = props;
630
+ const [isEditing, setIsEditing] = useState(false);
631
+ const [stringifiedValue, setStringifiedValue] = useState(JSON.stringify(data, null, 2));
632
+ const [error, setError] = useState(null);
633
+ const collectionSize = Object.keys(data).length;
634
+ const filterProps = { key: name, path, level: path.length, value: data, size: collectionSize };
635
+ const [collapsed, setCollapsed] = useState(collapseFilter(filterProps));
636
+ useEffect(() => {
637
+ setStringifiedValue(JSON.stringify(data, null, 2));
638
+ }, [data]);
639
+ useEffect(() => {
640
+ setCollapsed(collapseFilter(filterProps));
641
+ }, [collapseFilter]);
642
+ const collectionType = Array.isArray(data) ? 'array' : 'object';
643
+ const brackets = collectionType === 'array' ? { open: '[', close: ']' } : { open: '{', close: '}' };
644
+ const transitionTime = getComputedStyle(document.documentElement).getPropertyValue('--jer-expand-transition-time');
645
+ const handleKeyPress = (e) => {
646
+ if (e.key === 'Enter' && (e.metaKey || e.shiftKey || e.ctrlKey))
647
+ handleEdit();
648
+ else if (e.key === 'Escape')
649
+ handleCancel();
650
+ };
651
+ const showError = (errorString) => {
652
+ setError(errorString);
653
+ setTimeout(() => setError(null), ERROR_DISPLAY_TIME);
654
+ console.log('Error', errorString);
655
+ };
656
+ const handleEdit = () => {
657
+ try {
658
+ const value = JSON.parse(stringifiedValue);
659
+ setIsEditing(false);
660
+ setError(null);
661
+ onEdit(value, path).then((error) => {
662
+ if (error)
663
+ showError(error);
664
+ });
665
+ }
666
+ catch {
667
+ setError(translate('ERROR_INVALID_JSON'));
668
+ setTimeout(() => setError(null), ERROR_DISPLAY_TIME);
669
+ console.log('Invalid JSON');
670
+ return;
671
+ }
672
+ };
673
+ const handleAdd = (key) => {
674
+ setCollapsed(false);
675
+ if (collectionType === 'array') {
676
+ onAdd(defaultValue, [...path, data.length]).then((error) => {
677
+ if (error)
678
+ showError(error);
679
+ });
680
+ }
681
+ else if (key in data) {
682
+ showError(translate('ERROR_KEY_EXISTS'));
683
+ return;
684
+ }
685
+ else
686
+ onAdd(defaultValue, [...path, key]).then((error) => {
687
+ if (error)
688
+ showError(error);
689
+ });
690
+ };
691
+ const handleDelete = path.length > 0
692
+ ? () => {
693
+ onDelete(data, path).then((result) => {
694
+ if (result)
695
+ showError(result);
696
+ });
697
+ }
698
+ : undefined;
699
+ const handleCancel = () => {
700
+ setIsEditing(false);
701
+ setError(null);
702
+ setStringifiedValue(JSON.stringify(data, null, 2));
703
+ };
704
+ const canEdit = useMemo(() => !restrictEditFilter(filterProps), [filterProps]);
705
+ const canDelete = useMemo(() => !restrictDeleteFilter(filterProps), [filterProps]);
706
+ const canAdd = useMemo(() => !restrictAddFilter(filterProps), [filterProps]);
707
+ const showLabel = showArrayIndices || !(typeof path.slice(-1)[0] === 'number');
708
+ const keyValueArray = Object.entries(data).map(([key, value]) => [
709
+ collectionType === 'array' ? Number(key) : key,
710
+ value,
711
+ ]);
712
+ if (keySort && collectionType === 'object')
713
+ keyValueArray.sort(typeof keySort === 'function' ? (a, b) => keySort(a[0], b[0]) : undefined);
714
+ const numOfLines = JSON.stringify(data, null, 2).split('\n').length;
715
+ return (jsxs("div", { className: "jer-component fb-collection-component", style: { marginLeft: `${path.length === 0 ? 0 : indent / 2}em` }, children: [jsxs("div", { className: "jer-collection-header-row", children: [jsx("div", { onClick: () => {
716
+ if (!isEditing)
717
+ setCollapsed(!collapsed);
718
+ }, children: jsx(Icon, { name: "chevron", rotate: collapsed }) }), jsxs("div", { className: "jer-collection-name", children: [jsx("span", { style: styles.property, children: showLabel ? `${name}:` : null }), jsx("span", { className: "jer-brackets", style: styles.bracket, children: brackets.open })] }), jsx("div", { className: "jer-collection-item-count", style: styles.itemCount, children: collectionSize === 1
719
+ ? translate('ITEM_SINGLE', 1)
720
+ : translate('ITEMS_MULTIPLE', collectionSize) }), collapsed && (jsx("div", { className: "jer-brackets", style: styles.bracket, children: brackets.close })), !isEditing && (jsx(EditButtons, { startEdit: canEdit
721
+ ? () => {
722
+ setIsEditing(true);
723
+ setCollapsed(false);
724
+ }
725
+ : undefined, handleAdd: canAdd ? handleAdd : undefined, handleDelete: canDelete ? handleDelete : undefined, enableClipboard: enableClipboard, type: collectionType, data: data, name: name, path: path, translate: translate }))] }), jsxs("div", { className: 'jer-collection-inner', style: {
726
+ maxHeight: collapsed ? 0 : `${numOfLines * 1.6}em`,
727
+ overflowY: collapsed ? 'hidden' : 'visible',
728
+ transition: `max-height ${transitionTime}`,
729
+ }, children: [isEditing ? (jsxs("div", { className: "jer-collection-text-edit", children: [jsx(AutogrowTextArea, { className: "jer-collection-text-area", name: path.join('.'), value: stringifiedValue, setValue: setStringifiedValue, isEditing: isEditing, handleKeyPress: handleKeyPress }), jsx("div", { className: "jer-collection-input-button-row", children: jsx(InputButtons, { onOk: handleEdit, onCancel: handleCancel, isCollection: true }) })] })) : (jsx(Fragment, { children: keyValueArray.map(([key, value]) => (jsx("div", { className: "jer-collection-element", children: isCollection(value) ? (jsx(CollectionNode, { data: value, path: [...path, key], name: key, ...props }, key)) : (jsx(ValueNodeWrapper, { data: value, path: [...path, key], name: key, ...props, showArrayIndices: collectionType === 'object' ? true : showArrayIndices }, key)) }, key))) })), jsx("div", { className: isEditing ? 'jer-collection-error-row' : 'jer-collection-error-row-edit', children: error && (jsx("span", { className: "jer-error-slug", style: styles.error, children: error })) })] }), !collapsed && (jsx("div", { className: "jer-brackets", style: styles.bracket, children: brackets.close }))] }));
730
+ };
731
+
732
+ const localisedStrings = {
733
+ ITEM_SINGLE: '{{count}} item',
734
+ ITEMS_MULTIPLE: '{{count}} items',
735
+ KEY_NEW: 'Enter new key',
736
+ ERROR_KEY_EXISTS: 'Key already exists',
737
+ ERROR_INVALID_JSON: 'Invalid JSON',
738
+ ERROR_UPDATE: 'Update unsuccessful',
739
+ ERROR_DELETE: 'Delete unsuccessful',
740
+ ERROR_ADD: 'Adding node unsuccessful',
741
+ };
742
+ const translate = (translations, key, count) => {
743
+ const string = key in translations ? translations[key] : localisedStrings[key];
744
+ return count === undefined ? string : string?.replace('{{count}}', String(count));
745
+ };
746
+ const getTranslateFunction = (translations) => (key, count) => translate(translations, key, count);
747
+
748
+ const Editor = ({ data: srcData, rootName = 'root', onUpdate, onEdit: srcEdit = onUpdate, onDelete: srcDelete = onUpdate, onAdd: srcAdd = onUpdate, enableClipboard = true, theme = 'default', icons, indent = 4, collapse = false, restrictEdit = false, restrictDelete = false, restrictAdd = false, keySort = false, showArrayIndices = true, defaultValue = null, minWidth = 250, maxWidth = 600, stringTruncate = 250, translations = {}, className, }) => {
749
+ const [data, setData] = useState(srcData);
750
+ const { styles, setTheme, setIcons } = useTheme();
751
+ const collapseFilter = useCallback(getFilterFunction(collapse), [collapse]);
752
+ const translate = useCallback(getTranslateFunction(translations), [translations]);
753
+ useEffect(() => {
754
+ setData(srcData);
755
+ }, [srcData]);
756
+ useEffect(() => {
757
+ if (theme)
758
+ setTheme(theme);
759
+ if (icons)
760
+ setIcons(icons);
761
+ }, [theme, icons]);
762
+ const onEdit = async (value, path) => {
763
+ const { currentData, newData, currentValue, newValue } = updateDataObject(data, path, value, 'update');
764
+ if (srcEdit) {
765
+ const result = await srcEdit({
766
+ currentData,
767
+ newData,
768
+ currentValue,
769
+ newValue,
770
+ name: path.slice(-1)[0],
771
+ path,
772
+ });
773
+ if (result === undefined)
774
+ setData(newData);
775
+ if (result === false)
776
+ return translate('ERROR_UPDATE');
777
+ return result;
778
+ }
779
+ else
780
+ setData(newData);
781
+ };
782
+ const onDelete = async (value, path) => {
783
+ const { currentData, newData, currentValue, newValue } = updateDataObject(data, path, value, 'delete');
784
+ if (srcDelete) {
785
+ const result = await srcDelete({
786
+ currentData,
787
+ newData,
788
+ currentValue,
789
+ newValue,
790
+ name: path.slice(-1)[0],
791
+ path,
792
+ });
793
+ if (result === undefined)
794
+ setData(newData);
795
+ if (result === false)
796
+ return translate('ERROR_DELETE');
797
+ return result;
798
+ }
799
+ else
800
+ setData(newData);
801
+ };
802
+ const onAdd = async (value, path) => {
803
+ const { currentData, newData, currentValue, newValue } = updateDataObject(data, path, value, 'add');
804
+ if (srcAdd) {
805
+ const result = await srcAdd({
806
+ currentData,
807
+ newData,
808
+ currentValue,
809
+ newValue,
810
+ name: path.slice(-1)[0],
811
+ path,
812
+ });
813
+ if (result === undefined)
814
+ setData(newData);
815
+ if (result === false)
816
+ return translate('ERROR_ADD');
817
+ return result;
818
+ }
819
+ else
820
+ setData(newData);
821
+ };
822
+ const restrictEditFilter = getFilterFunction(restrictEdit);
823
+ const restrictDeleteFilter = getFilterFunction(restrictDelete);
824
+ const restrictAddFilter = getFilterFunction(restrictAdd);
825
+ const otherProps = {
826
+ name: rootName,
827
+ onEdit,
828
+ onDelete,
829
+ onAdd,
830
+ collapseFilter,
831
+ restrictEditFilter,
832
+ restrictDeleteFilter,
833
+ restrictAddFilter,
834
+ enableClipboard,
835
+ keySort,
836
+ showArrayIndices,
837
+ indent,
838
+ defaultValue,
839
+ stringTruncate,
840
+ translate,
841
+ };
842
+ if (!styles)
843
+ return null;
844
+ return (jsx("div", { className: 'jer-editor-container ' + className, style: { ...styles.container, minWidth, maxWidth }, children: isCollection(data) && jsx(CollectionNode, { data: data, path: [], ...otherProps }) }));
845
+ };
846
+ const JsonEditor = (props) => (jsx(ThemeProvider, { children: jsx(Editor, { ...props }) }));
847
+ const updateDataObject = (data, path, newValue, action) => {
848
+ if (path.length === 0) {
849
+ return {
850
+ currentData: data,
851
+ newData: newValue,
852
+ currentValue: data,
853
+ newValue: newValue,
854
+ };
855
+ }
856
+ const currentValue = action !== 'add' ? extract(data, path) : undefined;
857
+ const newData = assign(clone(data), path, newValue, { remove: action === 'delete' });
858
+ return {
859
+ currentData: data,
860
+ newData,
861
+ currentValue,
862
+ newValue: action === 'update' ? newValue : undefined,
863
+ };
864
+ };
865
+ const getFilterFunction = (propValue) => {
866
+ if (typeof propValue === 'boolean')
867
+ return () => propValue;
868
+ if (typeof propValue === 'number')
869
+ return ({ level }) => level >= propValue;
870
+ return propValue;
871
+ };
872
+
873
+ export { JsonEditor, themes };