json-edit-react 0.9.6 → 1.1.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 +102 -31
- package/build/index.cjs.js +207 -51
- package/build/index.d.ts +44 -4
- package/build/index.esm.js +208 -49
- package/package.json +11 -11
package/build/index.esm.js
CHANGED
|
@@ -24,7 +24,7 @@ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
|
24
24
|
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
25
25
|
PERFORMANCE OF THIS SOFTWARE.
|
|
26
26
|
***************************************************************************** */
|
|
27
|
-
/* global Reflect, Promise */
|
|
27
|
+
/* global Reflect, Promise, SuppressedError, Symbol */
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
function __rest(s, e) {
|
|
@@ -47,7 +47,12 @@ function __awaiter(thisArg, _arguments, P, generator) {
|
|
|
47
47
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
48
48
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
49
49
|
});
|
|
50
|
-
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
53
|
+
var e = new Error(message);
|
|
54
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
55
|
+
};
|
|
51
56
|
|
|
52
57
|
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}}
|
|
53
58
|
|
|
@@ -310,7 +315,7 @@ const ThemeProvider = ({ children }) => {
|
|
|
310
315
|
const [styles, setStyles] = useState(emptyStyleObject);
|
|
311
316
|
const [icons, setIcons] = useState({});
|
|
312
317
|
const setTheme = (theme) => setStyles(compileStyles(theme));
|
|
313
|
-
return (jsx(ThemeProviderContext.Provider,
|
|
318
|
+
return (jsx(ThemeProviderContext.Provider, { value: { styles, setTheme, icons, setIcons }, children: children }));
|
|
314
319
|
};
|
|
315
320
|
const useTheme = () => useContext(ThemeProviderContext);
|
|
316
321
|
const compileStyles = (themeInput) => {
|
|
@@ -384,10 +389,10 @@ const defaultStyleProperties = {
|
|
|
384
389
|
const AutogrowTextArea = ({ className, name, value, setValue, handleKeyPress, }) => {
|
|
385
390
|
const { styles } = useTheme();
|
|
386
391
|
const dummyValue = value.slice(-1) === '\n' ? value + '.' : value;
|
|
387
|
-
return (jsxs("div",
|
|
392
|
+
return (jsxs("div", { style: { display: 'grid' }, children: [jsx("textarea", { style: Object.assign({ height: 'auto', gridArea: '1 / 1 / 2 / 2', overflowY: 'auto' }, styles.input), rows: 1, className: className, name: name, value: value, onChange: (e) => setValue(e.target.value), autoFocus: true, onFocus: (e) => {
|
|
388
393
|
if (value.length < 40)
|
|
389
394
|
e.target.select();
|
|
390
|
-
}, onKeyDown: handleKeyPress }), jsx("span",
|
|
395
|
+
}, onKeyDown: handleKeyPress }), jsx("span", { className: className, style: Object.assign({ visibility: 'hidden', height: 'auto', gridArea: '1 / 1 / 2 / 2', color: 'red', opacity: 0.9, whiteSpace: 'pre-wrap', overflow: 'clip', border: '1px solid transparent' }, styles.input), children: dummyValue })] }));
|
|
391
396
|
};
|
|
392
397
|
|
|
393
398
|
const INVALID_FUNCTION_STRING = '**INVALID_FUNCTION**';
|
|
@@ -401,10 +406,10 @@ const StringValue = ({ value, setValue, isEditing, path, setIsEditing, handleEdi
|
|
|
401
406
|
handleCancel();
|
|
402
407
|
};
|
|
403
408
|
const breakString = (text) => text.split('\n').map((line, index, arr) => (jsxs("span", { children: [line, index < arr.length - 1 ? jsx("br", {}) : null] }, index)));
|
|
404
|
-
return isEditing ? (jsx(AutogrowTextArea, { className: "jer-input-text", name: path.join('.'), value: value, setValue: setValue, isEditing: isEditing, handleKeyPress: handleKeyPress })) : (jsxs("div",
|
|
409
|
+
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) => {
|
|
405
410
|
if (e.getModifierState('Control') || e.getModifierState('Meta'))
|
|
406
411
|
setIsEditing(true);
|
|
407
|
-
}, className: "jer-value-string", style: styles.string
|
|
412
|
+
}, className: "jer-value-string", style: styles.string, children: ["\"", breakString(truncate(value, stringTruncate)), "\""] }));
|
|
408
413
|
};
|
|
409
414
|
const NumberValue = ({ value, setValue, isEditing, path, setIsEditing, handleEdit, handleCancel, }) => {
|
|
410
415
|
const { styles } = useTheme();
|
|
@@ -417,18 +422,70 @@ const NumberValue = ({ value, setValue, isEditing, path, setIsEditing, handleEdi
|
|
|
417
422
|
const validateNumber = (input) => {
|
|
418
423
|
return input.replace(/[^0-9.-]/g, '');
|
|
419
424
|
};
|
|
420
|
-
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",
|
|
425
|
+
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 }));
|
|
421
426
|
};
|
|
422
|
-
const BooleanValue = ({ value, setValue, isEditing, path, setIsEditing, }) => {
|
|
427
|
+
const BooleanValue = ({ value, setValue, isEditing, path, setIsEditing, handleEdit, handleCancel, }) => {
|
|
423
428
|
const { styles } = useTheme();
|
|
424
|
-
|
|
429
|
+
useEffect(() => {
|
|
430
|
+
if (isEditing)
|
|
431
|
+
document.addEventListener('keydown', listenForSubmit);
|
|
432
|
+
return () => document.removeEventListener('keydown', listenForSubmit);
|
|
433
|
+
}, [isEditing]);
|
|
434
|
+
const listenForSubmit = (event) => {
|
|
435
|
+
if (event.key === 'Enter') {
|
|
436
|
+
handleEdit();
|
|
437
|
+
}
|
|
438
|
+
else if (event.key === 'Escape')
|
|
439
|
+
handleCancel();
|
|
440
|
+
};
|
|
441
|
+
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) }));
|
|
425
442
|
};
|
|
426
|
-
const NullValue = ({ value, isEditing, setIsEditing }) => {
|
|
443
|
+
const NullValue = ({ value, isEditing, setIsEditing, handleEdit, handleCancel, }) => {
|
|
427
444
|
const { styles } = useTheme();
|
|
428
|
-
|
|
445
|
+
useEffect(() => {
|
|
446
|
+
if (isEditing)
|
|
447
|
+
document.addEventListener('keydown', listenForSubmit);
|
|
448
|
+
return () => document.removeEventListener('keydown', listenForSubmit);
|
|
449
|
+
}, [isEditing]);
|
|
450
|
+
const listenForSubmit = (event) => {
|
|
451
|
+
if (event.key === 'Enter') {
|
|
452
|
+
handleEdit();
|
|
453
|
+
}
|
|
454
|
+
else if (event.key === 'Escape')
|
|
455
|
+
handleCancel();
|
|
456
|
+
};
|
|
457
|
+
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) }));
|
|
458
|
+
};
|
|
459
|
+
const ObjectValue = ({ value, translate, isEditing, handleEdit, handleCancel, }) => {
|
|
460
|
+
useEffect(() => {
|
|
461
|
+
if (isEditing)
|
|
462
|
+
document.addEventListener('keydown', listenForSubmit);
|
|
463
|
+
return () => document.removeEventListener('keydown', listenForSubmit);
|
|
464
|
+
}, []);
|
|
465
|
+
const listenForSubmit = (event) => {
|
|
466
|
+
if (event.key === 'Enter') {
|
|
467
|
+
handleEdit();
|
|
468
|
+
}
|
|
469
|
+
else if (event.key === 'Escape')
|
|
470
|
+
handleCancel();
|
|
471
|
+
};
|
|
472
|
+
return (jsx("span", { className: "jer-value-object", children: `{${translate('DEFAULT_NEW_KEY')}: "${value}" }` }));
|
|
473
|
+
};
|
|
474
|
+
const ArrayValue = ({ value, isEditing, handleEdit, handleCancel, }) => {
|
|
475
|
+
useEffect(() => {
|
|
476
|
+
if (isEditing)
|
|
477
|
+
document.addEventListener('keydown', listenForSubmit);
|
|
478
|
+
return () => document.removeEventListener('keydown', listenForSubmit);
|
|
479
|
+
}, []);
|
|
480
|
+
const listenForSubmit = (event) => {
|
|
481
|
+
if (event.key === 'Enter') {
|
|
482
|
+
handleEdit();
|
|
483
|
+
}
|
|
484
|
+
else if (event.key === 'Escape')
|
|
485
|
+
handleCancel();
|
|
486
|
+
};
|
|
487
|
+
return jsx("span", { className: "jer-value-array", children: `[${value === null ? '' : value}]` });
|
|
429
488
|
};
|
|
430
|
-
const ObjectValue = () => (jsx("span", Object.assign({ className: "jer-value-object" }, { children: '{ }' })));
|
|
431
|
-
const ArrayValue = ({ value }) => (jsx("span", Object.assign({ className: "jer-value-array" }, { children: `[${value === null ? '' : value}]` })));
|
|
432
489
|
const InvalidValue = ({ value }) => {
|
|
433
490
|
let message = 'Error!';
|
|
434
491
|
switch (typeof value) {
|
|
@@ -443,7 +500,7 @@ const InvalidValue = ({ value }) => {
|
|
|
443
500
|
message = 'Symbol';
|
|
444
501
|
break;
|
|
445
502
|
}
|
|
446
|
-
return jsx("span",
|
|
503
|
+
return jsx("span", { className: "jer-value-invalid", children: message });
|
|
447
504
|
};
|
|
448
505
|
|
|
449
506
|
const Icon = ({ name, rotate }) => {
|
|
@@ -507,22 +564,22 @@ const EditButtons = ({ startEdit, handleDelete, handleAdd, enableClipboard, type
|
|
|
507
564
|
if (typeof enableClipboard === 'function')
|
|
508
565
|
enableClipboard({ value, stringValue, path, key: name, type: copyType });
|
|
509
566
|
};
|
|
510
|
-
return (jsxs("div",
|
|
567
|
+
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: () => {
|
|
511
568
|
if (type === 'object')
|
|
512
569
|
setIsAdding(true);
|
|
513
570
|
else
|
|
514
571
|
handleAdd('');
|
|
515
|
-
}
|
|
572
|
+
}, 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: Object.assign({}, styles.input) }), jsx(InputButtons, { onOk: () => {
|
|
516
573
|
if (!!newKey) {
|
|
517
574
|
setIsAdding(false);
|
|
518
575
|
handleAdd(newKey);
|
|
519
576
|
}
|
|
520
577
|
}, onCancel: () => {
|
|
521
578
|
setIsAdding(false);
|
|
522
|
-
} })] }))] }))
|
|
579
|
+
} })] }))] }));
|
|
523
580
|
};
|
|
524
581
|
const InputButtons = ({ onOk, onCancel }) => {
|
|
525
|
-
return (jsxs("div",
|
|
582
|
+
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" }) })] }));
|
|
526
583
|
};
|
|
527
584
|
const stringifyPath = (path) => path.reduce((str, part) => {
|
|
528
585
|
if (typeof part === 'number')
|
|
@@ -536,9 +593,27 @@ const ValueDataTypes = ['string', 'number', 'boolean', 'null'];
|
|
|
536
593
|
const CollectionDataTypes = ['object', 'array'];
|
|
537
594
|
const DataTypes = [...ValueDataTypes, ...CollectionDataTypes];
|
|
538
595
|
|
|
539
|
-
const
|
|
596
|
+
const getCustomNode = (customNodeDefinitions = [], filterProps) => {
|
|
597
|
+
const matchingDefinitions = customNodeDefinitions.filter(({ condition }) => condition(filterProps));
|
|
598
|
+
if (matchingDefinitions.length === 0)
|
|
599
|
+
return {};
|
|
600
|
+
const { element, props, hideKey = false } = matchingDefinitions[0];
|
|
601
|
+
return { CustomNode: element, customNodeProps: props, hideKey };
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
const CustomNodeWrapper = ({ name, hideKey, children, indent, }) => {
|
|
605
|
+
const { styles } = useTheme();
|
|
606
|
+
const indentStyle = indent ? { marginLeft: `${indent / 2}em` } : {};
|
|
607
|
+
return (jsx("div", { className: "jer-component jer-value-component", style: indentStyle, children: jsxs("div", { className: "jer-value-main-row", style: {
|
|
608
|
+
flexWrap: name.length > 10 ? 'wrap' : 'nowrap',
|
|
609
|
+
}, children: [!hideKey && (jsxs("label", { htmlFor: name, className: "jer-object-key", style: Object.assign(Object.assign({}, styles.property), { minWidth: `${Math.min(String(name).length + 1, 5)}ch`, flexShrink: name.length > 10 ? 1 : 0 }), children: [name, ":", ' '] })), jsx("div", { className: "jer-value-and-buttons", style: { paddingLeft: hideKey ? 0 : undefined }, children: children })] }) }));
|
|
610
|
+
};
|
|
611
|
+
|
|
612
|
+
const ValueNodeWrapper = (props) => {
|
|
613
|
+
const { data, parentData, name, path, onEdit, onDelete, enableClipboard, restrictEditFilter, restrictDeleteFilter, restrictTypeSelection, showLabel, stringTruncate, indent, translate, customNodeDefinitions, } = props;
|
|
540
614
|
const { styles } = useTheme();
|
|
541
615
|
const [isEditing, setIsEditing] = useState(false);
|
|
616
|
+
const [isEditingKey, setIsEditingKey] = useState(false);
|
|
542
617
|
const [value, setValue] = useState(typeof data === 'function' ? INVALID_FUNCTION_STRING : data);
|
|
543
618
|
const [error, setError] = useState(null);
|
|
544
619
|
const [dataType, setDataType] = useState(getDataType(data));
|
|
@@ -547,8 +622,14 @@ const ValueNodeWrapper = ({ data, name, path, onEdit, onDelete, enableClipboard,
|
|
|
547
622
|
setDataType(getDataType(data));
|
|
548
623
|
}, [data, error]);
|
|
549
624
|
const handleChangeDataType = (type) => {
|
|
550
|
-
|
|
551
|
-
|
|
625
|
+
const customNode = customNodeDefinitions.find((customNode) => customNode.name === type);
|
|
626
|
+
if (customNode) {
|
|
627
|
+
onEdit(customNode.defaultValue, path);
|
|
628
|
+
}
|
|
629
|
+
else {
|
|
630
|
+
setValue(convertValue(value, type));
|
|
631
|
+
setDataType(type);
|
|
632
|
+
}
|
|
552
633
|
};
|
|
553
634
|
const logError = (errorString) => {
|
|
554
635
|
setError(errorString);
|
|
@@ -560,10 +641,10 @@ const ValueNodeWrapper = ({ data, name, path, onEdit, onDelete, enableClipboard,
|
|
|
560
641
|
let newValue;
|
|
561
642
|
switch (dataType) {
|
|
562
643
|
case 'object':
|
|
563
|
-
newValue = {};
|
|
644
|
+
newValue = { [translate('DEFAULT_NEW_KEY')]: value };
|
|
564
645
|
break;
|
|
565
646
|
case 'array':
|
|
566
|
-
newValue = value !== null ?
|
|
647
|
+
newValue = value !== null ? value : [];
|
|
567
648
|
break;
|
|
568
649
|
case 'number':
|
|
569
650
|
const n = Number(value);
|
|
@@ -580,9 +661,27 @@ const ValueNodeWrapper = ({ data, name, path, onEdit, onDelete, enableClipboard,
|
|
|
580
661
|
logError(error);
|
|
581
662
|
});
|
|
582
663
|
};
|
|
664
|
+
const handleEditKey = (newKey) => {
|
|
665
|
+
setIsEditingKey(false);
|
|
666
|
+
if (!parentData)
|
|
667
|
+
return;
|
|
668
|
+
const parentPath = path.slice(0, -1);
|
|
669
|
+
if (!newKey)
|
|
670
|
+
return;
|
|
671
|
+
const newData = Object.fromEntries(Object.entries(parentData).map(([key, val]) => (key === name ? [newKey, val] : [key, val])));
|
|
672
|
+
onEdit(newData, parentPath);
|
|
673
|
+
};
|
|
674
|
+
const handleKeyPress = (e) => {
|
|
675
|
+
if (e.key === 'Enter')
|
|
676
|
+
handleEditKey(e.target.value);
|
|
677
|
+
else if (e.key === 'Escape')
|
|
678
|
+
handleCancel();
|
|
679
|
+
};
|
|
583
680
|
const handleCancel = () => {
|
|
584
681
|
setIsEditing(false);
|
|
682
|
+
setIsEditingKey(false);
|
|
585
683
|
setValue(data);
|
|
684
|
+
setDataType(getDataType(data));
|
|
586
685
|
};
|
|
587
686
|
const handleDelete = () => {
|
|
588
687
|
onDelete(value, path).then((error) => {
|
|
@@ -593,8 +692,11 @@ const ValueNodeWrapper = ({ data, name, path, onEdit, onDelete, enableClipboard,
|
|
|
593
692
|
const filterProps = { key: name, path, level: path.length, value: data, size: null };
|
|
594
693
|
const canEdit = useMemo(() => !restrictEditFilter(filterProps), [filterProps]);
|
|
595
694
|
const canDelete = useMemo(() => !restrictDeleteFilter(filterProps), [filterProps]);
|
|
695
|
+
const isArray = typeof path.slice(-1)[0] === 'number';
|
|
696
|
+
const canEditKey = !isArray && canEdit && canDelete;
|
|
596
697
|
const inputProps = {
|
|
597
698
|
value,
|
|
699
|
+
parentData,
|
|
598
700
|
setValue,
|
|
599
701
|
isEditing,
|
|
600
702
|
setIsEditing: canEdit ? () => setIsEditing(true) : () => { },
|
|
@@ -602,11 +704,35 @@ const ValueNodeWrapper = ({ data, name, path, onEdit, onDelete, enableClipboard,
|
|
|
602
704
|
handleCancel,
|
|
603
705
|
path,
|
|
604
706
|
stringTruncate,
|
|
707
|
+
translate,
|
|
605
708
|
};
|
|
606
|
-
|
|
709
|
+
const { CustomNode, customNodeProps, hideKey } = getCustomNode(customNodeDefinitions, {
|
|
710
|
+
key: name,
|
|
711
|
+
path,
|
|
712
|
+
level: path.length,
|
|
713
|
+
value: data,
|
|
714
|
+
size: 0,
|
|
715
|
+
});
|
|
716
|
+
const allDataTypes = [
|
|
717
|
+
...customNodeDefinitions
|
|
718
|
+
.filter(({ showInTypesSelector = false }) => showInTypesSelector)
|
|
719
|
+
.map(({ name }) => name),
|
|
720
|
+
...DataTypes,
|
|
721
|
+
];
|
|
722
|
+
const allowedDataTypes = useMemo(() => {
|
|
723
|
+
if (typeof restrictTypeSelection === 'boolean')
|
|
724
|
+
return restrictTypeSelection ? [] : allDataTypes;
|
|
725
|
+
if (Array.isArray(restrictTypeSelection))
|
|
726
|
+
return restrictTypeSelection;
|
|
727
|
+
const result = restrictTypeSelection(filterProps);
|
|
728
|
+
if (typeof result === 'boolean')
|
|
729
|
+
return result ? [] : allDataTypes;
|
|
730
|
+
return result;
|
|
731
|
+
}, [filterProps, restrictTypeSelection]);
|
|
732
|
+
return CustomNode ? (jsx(CustomNodeWrapper, { name: name, hideKey: hideKey, indent: indent, children: jsx(CustomNode, Object.assign({ customProps: customNodeProps }, props)) })) : (jsx("div", { className: "jer-component jer-value-component", style: { marginLeft: `${indent / 2}em` }, children: jsxs("div", { className: "jer-value-main-row", style: {
|
|
607
733
|
flexWrap: name.length > 10 ? 'wrap' : 'nowrap',
|
|
608
|
-
}
|
|
609
|
-
!error && (jsx(EditButtons, { startEdit: canEdit ? () => setIsEditing(true) : undefined, handleDelete: canDelete ? handleDelete : undefined, data: data, enableClipboard: enableClipboard, name: name, path: path, translate: translate }))), isEditing && (jsxs("div",
|
|
734
|
+
}, children: [showLabel && !isEditingKey && (jsxs("label", { htmlFor: path.join('.'), className: "jer-object-key", style: Object.assign(Object.assign({}, styles.property), { minWidth: `${Math.min(String(name).length + 1, 5)}ch`, flexShrink: name.length > 10 ? 1 : 0 }), onDoubleClick: () => canEditKey && setIsEditingKey(true), children: [name, ":", ' '] })), showLabel && isEditingKey && (jsx("input", { className: "jer-object-key", type: "text", name: path.join('.'), defaultValue: name, autoFocus: true, onFocus: (e) => e.target.select(), onKeyDown: handleKeyPress, style: { width: `${String(name).length / 1.5 + 0.5}em` } })), jsxs("div", { className: "jer-value-and-buttons", children: [jsx("div", { className: "jer-input-component", children: getInputComponent(dataType, inputProps) }), isEditing ? (jsx(InputButtons, { onOk: handleEdit, onCancel: handleCancel })) : (dataType !== 'invalid' &&
|
|
735
|
+
!error && (jsx(EditButtons, { startEdit: canEdit ? () => setIsEditing(true) : undefined, handleDelete: canDelete ? handleDelete : undefined, data: data, enableClipboard: enableClipboard, name: name, path: path, translate: translate }))), isEditing && allowedDataTypes.length > 0 && (jsxs("div", { className: "jer-select", children: [jsx("select", { name: `${name}-type-select`, className: "jer-type-select", onChange: (e) => handleChangeDataType(e.target.value), value: dataType, children: allowedDataTypes.map((type) => (jsx("option", { value: type, children: type }, type))) }), jsx("span", { className: "focus" })] })), !isEditing && error && (jsx("span", { className: "jer-error-slug", style: styles.error, children: error }))] })] }) }));
|
|
610
736
|
};
|
|
611
737
|
const getDataType = (value) => {
|
|
612
738
|
if (typeof value === 'string')
|
|
@@ -631,7 +757,7 @@ const getInputComponent = (dataType, inputProps) => {
|
|
|
631
757
|
case 'null':
|
|
632
758
|
return jsx(NullValue, Object.assign({}, inputProps));
|
|
633
759
|
case 'object':
|
|
634
|
-
return jsx(ObjectValue, Object.assign({}, inputProps));
|
|
760
|
+
return jsx(ObjectValue, Object.assign({}, inputProps, { value: value }));
|
|
635
761
|
case 'array':
|
|
636
762
|
return jsx(ArrayValue, Object.assign({}, inputProps));
|
|
637
763
|
default:
|
|
@@ -650,7 +776,7 @@ const convertValue = (value, type) => {
|
|
|
650
776
|
case 'null':
|
|
651
777
|
return null;
|
|
652
778
|
case 'object':
|
|
653
|
-
return
|
|
779
|
+
return value;
|
|
654
780
|
case 'array':
|
|
655
781
|
return [value];
|
|
656
782
|
default:
|
|
@@ -660,10 +786,11 @@ const convertValue = (value, type) => {
|
|
|
660
786
|
|
|
661
787
|
const isCollection = (value) => value !== null && typeof value == 'object';
|
|
662
788
|
const CollectionNode = (_a) => {
|
|
663
|
-
var { data, path, name } = _a, props = __rest(_a, ["data", "path", "name"]);
|
|
789
|
+
var { data, path, name, parentData, showCollectionCount } = _a, props = __rest(_a, ["data", "path", "name", "parentData", "showCollectionCount"]);
|
|
664
790
|
const { styles } = useTheme();
|
|
665
|
-
const { onEdit, onAdd, onDelete, restrictEditFilter, restrictDeleteFilter, restrictAddFilter, collapseFilter, enableClipboard, indent, keySort, showArrayIndices, defaultValue, translate, } = props;
|
|
791
|
+
const { onEdit, onAdd, onDelete, restrictEditFilter, restrictDeleteFilter, restrictAddFilter, collapseFilter, enableClipboard, indent, keySort, showArrayIndices, defaultValue, translate, customNodeDefinitions, } = props;
|
|
666
792
|
const [isEditing, setIsEditing] = useState(false);
|
|
793
|
+
const [isEditingKey, setIsEditingKey] = useState(false);
|
|
667
794
|
const [stringifiedValue, setStringifiedValue] = useState(JSON.stringify(data, null, 2));
|
|
668
795
|
const [error, setError] = useState(null);
|
|
669
796
|
const collectionSize = Object.keys(data).length;
|
|
@@ -717,6 +844,22 @@ const CollectionNode = (_a) => {
|
|
|
717
844
|
return;
|
|
718
845
|
}
|
|
719
846
|
};
|
|
847
|
+
const handleEditKey = (newKey) => {
|
|
848
|
+
setIsEditingKey(false);
|
|
849
|
+
if (!parentData)
|
|
850
|
+
return;
|
|
851
|
+
const parentPath = path.slice(0, -1);
|
|
852
|
+
if (!newKey)
|
|
853
|
+
return;
|
|
854
|
+
const newData = Object.fromEntries(Object.entries(parentData).map(([key, val]) => (key === name ? [newKey, val] : [key, val])));
|
|
855
|
+
onEdit(newData, parentPath);
|
|
856
|
+
};
|
|
857
|
+
const handleKeyPressKeyEdit = (e) => {
|
|
858
|
+
if (e.key === 'Enter')
|
|
859
|
+
handleEditKey(e.target.value);
|
|
860
|
+
else if (e.key === 'Escape')
|
|
861
|
+
handleCancel();
|
|
862
|
+
};
|
|
720
863
|
const handleAdd = (key) => {
|
|
721
864
|
setCollapsed(false);
|
|
722
865
|
if (collectionType === 'array') {
|
|
@@ -751,7 +894,10 @@ const CollectionNode = (_a) => {
|
|
|
751
894
|
const canEdit = useMemo(() => !restrictEditFilter(filterProps), [filterProps]);
|
|
752
895
|
const canDelete = useMemo(() => !restrictDeleteFilter(filterProps), [filterProps]);
|
|
753
896
|
const canAdd = useMemo(() => !restrictAddFilter(filterProps), [filterProps]);
|
|
754
|
-
const
|
|
897
|
+
const canEditKey = parentData !== null && canEdit && canAdd && canDelete;
|
|
898
|
+
const isArray = typeof path.slice(-1)[0] === 'number';
|
|
899
|
+
const showLabel = showArrayIndices || !isArray;
|
|
900
|
+
const showCount = showCollectionCount === 'when-closed' ? collapsed : showCollectionCount;
|
|
755
901
|
const keyValueArray = Object.entries(data).map(([key, value]) => [
|
|
756
902
|
collectionType === 'array' ? Number(key) : key,
|
|
757
903
|
value,
|
|
@@ -759,20 +905,27 @@ const CollectionNode = (_a) => {
|
|
|
759
905
|
if (keySort && collectionType === 'object')
|
|
760
906
|
keyValueArray.sort(typeof keySort === 'function' ? (a, b) => keySort(a[0], b[0]) : undefined);
|
|
761
907
|
const numOfLines = JSON.stringify(data, null, 2).split('\n').length;
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
908
|
+
const { CustomNode, customNodeProps, hideKey } = getCustomNode(customNodeDefinitions, {
|
|
909
|
+
key: name,
|
|
910
|
+
path,
|
|
911
|
+
level: path.length,
|
|
912
|
+
value: data,
|
|
913
|
+
size: Object.keys(data).length,
|
|
914
|
+
});
|
|
915
|
+
return (jsx("div", { className: "jer-component fb-collection-component", style: { marginLeft: `${path.length === 0 ? 0 : indent / 2}em` }, children: CustomNode ? (jsx(CustomNodeWrapper, { name: name, hideKey: hideKey, children: jsx(CustomNode, Object.assign({ data: data, path: path, name: name, parentData: parentData, customProps: customNodeProps }, props)) })) : (jsxs(Fragment, { children: [jsxs("div", { className: "jer-collection-header-row", children: [jsx("div", { onClick: handleCollapse, children: jsx(Icon, { name: "chevron", rotate: collapsed }) }), jsxs("div", { className: "jer-collection-name", children: [!isEditingKey && (jsx("span", { style: styles.property, onDoubleClick: () => canEditKey && setIsEditingKey(true), children: showLabel && name !== '' && name !== undefined ? `${name}:` : null })), isEditingKey && (jsx("input", { className: "jer-collection-name", type: "text", name: path.join('.'), defaultValue: name, autoFocus: true, onFocus: (e) => e.target.select(), onKeyDown: handleKeyPressKeyEdit, style: { width: `${String(name).length / 1.5 + 0.5}em` } })), !isEditing && (jsx("span", { className: "jer-brackets", style: styles.bracket, children: brackets.open }))] }), !isEditing && showCollectionCount && (jsx("div", { className: `jer-collection-item-count${showCount ? ' jer-visible' : ' jer-hidden'}`, style: styles.itemCount, children: collectionSize === 1
|
|
916
|
+
? translate('ITEM_SINGLE', 1)
|
|
917
|
+
: translate('ITEMS_MULTIPLE', collectionSize) })), jsx("div", { className: `jer-brackets${collapsed ? ' jer-visible' : ' jer-hidden'}`, style: styles.bracket, children: brackets.close }), !isEditing && (jsx(EditButtons, { startEdit: canEdit
|
|
918
|
+
? () => {
|
|
919
|
+
setIsEditing(true);
|
|
920
|
+
setCollapsed(false);
|
|
921
|
+
}
|
|
922
|
+
: 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: {
|
|
923
|
+
maxHeight: collapsed ? 0 : !isEditing ? `${numOfLines * 3}em` : undefined,
|
|
924
|
+
overflowY: collapsed || isAnimating ? 'hidden' : 'visible',
|
|
925
|
+
transition: `max-height ${transitionTime}`,
|
|
926
|
+
}, children: [isEditing ? (jsx("div", { className: "jer-collection-text-edit", children: jsxs("div", { 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: !hasBeenOpened.current
|
|
927
|
+
? null
|
|
928
|
+
: keyValueArray.map(([key, value]) => (jsx("div", { className: "jer-collection-element", children: isCollection(value) ? (jsx(CollectionNode, Object.assign({ data: value, parentData: data, path: [...path, key], name: key, showCollectionCount: showCollectionCount }, props), key)) : (jsx(ValueNodeWrapper, Object.assign({ data: value, parentData: data, path: [...path, key], name: key }, props, { showLabel: 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 })) }), jsx("div", { className: "jer-brackets", style: styles.bracket, children: brackets.close })] })] })) }));
|
|
776
929
|
};
|
|
777
930
|
|
|
778
931
|
const localisedStrings = {
|
|
@@ -784,6 +937,8 @@ const localisedStrings = {
|
|
|
784
937
|
ERROR_UPDATE: 'Update unsuccessful',
|
|
785
938
|
ERROR_DELETE: 'Delete unsuccessful',
|
|
786
939
|
ERROR_ADD: 'Adding node unsuccessful',
|
|
940
|
+
DEFAULT_STRING: 'New data!',
|
|
941
|
+
DEFAULT_NEW_KEY: 'key',
|
|
787
942
|
};
|
|
788
943
|
const translate = (translations, key, count) => {
|
|
789
944
|
const string = key in translations ? translations[key] : localisedStrings[key];
|
|
@@ -791,7 +946,7 @@ const translate = (translations, key, count) => {
|
|
|
791
946
|
};
|
|
792
947
|
const getTranslateFunction = (translations) => (key, count) => translate(translations, key, count);
|
|
793
948
|
|
|
794
|
-
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 = 'min(600px, 90vw)', stringTruncate = 250, translations = {}, className, }) => {
|
|
949
|
+
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, showCollectionCount = true, restrictEdit = false, restrictDelete = false, restrictAdd = false, restrictTypeSelection = false, keySort = false, showArrayIndices = true, defaultValue = null, minWidth = 250, maxWidth = 'min(600px, 90vw)', stringTruncate = 250, translations = {}, className, customNodeDefinitions = [], }) => {
|
|
795
950
|
const [data, setData] = useState(srcData);
|
|
796
951
|
const { styles, setTheme, setIcons } = useTheme();
|
|
797
952
|
const collapseFilter = useCallback(getFilterFunction(collapse), [collapse]);
|
|
@@ -873,10 +1028,12 @@ const Editor = ({ data: srcData, rootName = 'root', onUpdate, onEdit: srcEdit =
|
|
|
873
1028
|
onEdit,
|
|
874
1029
|
onDelete,
|
|
875
1030
|
onAdd,
|
|
1031
|
+
showCollectionCount,
|
|
876
1032
|
collapseFilter,
|
|
877
1033
|
restrictEditFilter,
|
|
878
1034
|
restrictDeleteFilter,
|
|
879
1035
|
restrictAddFilter,
|
|
1036
|
+
restrictTypeSelection,
|
|
880
1037
|
enableClipboard,
|
|
881
1038
|
keySort,
|
|
882
1039
|
showArrayIndices,
|
|
@@ -884,10 +1041,12 @@ const Editor = ({ data: srcData, rootName = 'root', onUpdate, onEdit: srcEdit =
|
|
|
884
1041
|
defaultValue,
|
|
885
1042
|
stringTruncate,
|
|
886
1043
|
translate,
|
|
1044
|
+
customNodeDefinitions,
|
|
1045
|
+
parentData: null,
|
|
887
1046
|
};
|
|
888
1047
|
if (!styles)
|
|
889
1048
|
return null;
|
|
890
|
-
return (jsx("div",
|
|
1049
|
+
return (jsx("div", { className: 'jer-editor-container ' + className, style: Object.assign(Object.assign({}, styles.container), { minWidth, maxWidth }), children: isCollection(data) ? (jsx(CollectionNode, Object.assign({ data: data, path: [] }, otherProps))) : (jsx(ValueNodeWrapper, Object.assign({ data: data, path: [], showLabel: true }, otherProps))) }));
|
|
891
1050
|
};
|
|
892
1051
|
const JsonEditor = (props) => (jsx(ThemeProvider, { children: jsx(Editor, Object.assign({}, props)) }));
|
|
893
1052
|
const updateDataObject = (data, path, newValue, action) => {
|
|
@@ -916,4 +1075,4 @@ const getFilterFunction = (propValue) => {
|
|
|
916
1075
|
return propValue;
|
|
917
1076
|
};
|
|
918
1077
|
|
|
919
|
-
export { JsonEditor,
|
|
1078
|
+
export { JsonEditor, themes };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "json-edit-react",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "React component for editing or viewing JSON/object data",
|
|
5
5
|
"main": "build/index.cjs.js",
|
|
6
6
|
"module": "build/index.esm.js",
|
|
@@ -27,22 +27,22 @@
|
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"just-clone": "^6.2.0",
|
|
29
29
|
"object-property-assigner": "^1.0.1",
|
|
30
|
-
"object-property-extractor": "^1.0.
|
|
31
|
-
"react-icons": "^4.
|
|
30
|
+
"object-property-extractor": "^1.0.7",
|
|
31
|
+
"react-icons": "^4.12.0"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"@rollup/plugin-typescript": "^11.1.
|
|
35
|
-
"@types/node": "^20.
|
|
36
|
-
"@types/react": "^18.2.
|
|
37
|
-
"rimraf": "^5.0.
|
|
38
|
-
"rollup": "^
|
|
39
|
-
"rollup-plugin-dts": "^
|
|
34
|
+
"@rollup/plugin-typescript": "^11.1.5",
|
|
35
|
+
"@types/node": "^20.9.0",
|
|
36
|
+
"@types/react": "^18.2.37",
|
|
37
|
+
"rimraf": "^5.0.5",
|
|
38
|
+
"rollup": "^4.4.1",
|
|
39
|
+
"rollup-plugin-dts": "^6.1.0",
|
|
40
40
|
"rollup-plugin-peer-deps-external": "^2.2.4",
|
|
41
41
|
"rollup-plugin-sizes": "^1.0.5",
|
|
42
42
|
"rollup-plugin-styles": "^4.0.0",
|
|
43
43
|
"rollup-plugin-terser": "^7.0.2",
|
|
44
44
|
"ts-node": "^10.9.1",
|
|
45
|
-
"tslib": "^2.
|
|
46
|
-
"typescript": "^5.
|
|
45
|
+
"tslib": "^2.6.2",
|
|
46
|
+
"typescript": "^5.2.2"
|
|
47
47
|
}
|
|
48
48
|
}
|