json-edit-react 0.9.6 → 1.0.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.
@@ -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, Object.assign({ value: { styles, setTheme, icons, setIcons } }, { children: children })));
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", Object.assign({ 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) => {
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", Object.assign({ 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 }))] })));
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", Object.assign({ onDoubleClick: () => setIsEditing(true), onClick: (e) => {
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 }, { children: ["\"", breakString(truncate(value, stringTruncate)), "\""] })));
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", Object.assign({ onDoubleClick: () => setIsEditing(true), className: "jer-value-number", style: styles.number }, { children: value })));
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
- return isEditing ? (jsx("input", { className: "jer-input-boolean", type: "checkbox", name: path.join('.'), checked: value, onChange: () => setValue(!value) })) : (jsx("span", Object.assign({ onDoubleClick: () => setIsEditing(true), className: "jer-value-boolean", style: styles.boolean }, { children: String(value) })));
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
- return isEditing ? (jsx("div", Object.assign({ className: "jer-input-null" }, { children: "null" }))) : (jsx("div", Object.assign({ onDoubleClick: () => setIsEditing(true), className: "jer-value-null", style: styles.null }, { children: String(value) })));
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", Object.assign({ className: "jer-value-invalid" }, { children: message }));
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", Object.assign({ className: "jer-edit-buttons", style: isAdding ? { opacity: 1 } : undefined }, { children: [enableClipboard && (jsx("div", Object.assign({ onClick: handleCopy, className: "jer-copy-pulse" }, { children: jsx(Icon, { name: "copy" }) }))), startEdit && (jsx("div", Object.assign({ onClick: startEdit }, { children: jsx(Icon, { name: "edit" }) }))), handleDelete && (jsx("div", Object.assign({ onClick: handleDelete }, { children: jsx(Icon, { name: "delete" }) }))), handleAdd && (jsx("div", Object.assign({ onClick: () => {
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
- } }, { 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: () => {
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", Object.assign({ className: "jer-confirm-buttons" }, { children: [jsx("div", Object.assign({ onClick: onOk }, { children: jsx(Icon, { name: "ok" }) })), jsx("div", Object.assign({ onClick: onCancel }, { children: jsx(Icon, { name: "cancel" }) }))] })));
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 ValueNodeWrapper = ({ data, name, path, onEdit, onDelete, enableClipboard, restrictEditFilter, restrictDeleteFilter, showArrayIndices, stringTruncate, indent, translate, }) => {
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
- setValue(convertValue(value, type));
551
- setDataType(type);
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 ? [value] : [];
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
- return (jsx("div", Object.assign({ className: "jer-component jer-value-component", style: { marginLeft: `${indent / 2}em` } }, { children: jsxs("div", Object.assign({ className: "jer-value-main-row", style: {
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
- } }, { children: [showArrayIndices && (jsxs("label", Object.assign({ 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 }) }, { children: [name, ":", ' '] }))), jsxs("div", Object.assign({ className: "jer-value-and-buttons" }, { children: [jsx("div", Object.assign({ className: "jer-input-component" }, { children: getInputComponent(dataType, inputProps) })), isEditing ? (jsx(InputButtons, { onOk: handleEdit, onCancel: handleCancel })) : (dataType !== 'invalid' &&
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", Object.assign({ className: "jer-select" }, { children: [jsx("select", Object.assign({ name: `${name}-type-select`, className: "jer-type-select", onChange: (e) => handleChangeDataType(e.target.value), value: dataType }, { children: DataTypes.map((type) => (jsx("option", Object.assign({ value: type }, { children: type }), type))) })), jsx("span", { className: "focus" })] }))), !isEditing && error && (jsx("span", Object.assign({ className: "jer-error-slug", style: styles.error }, { children: error })))] }))] })) })));
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 showLabel = showArrayIndices || !(typeof path.slice(-1)[0] === 'number');
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
- return (jsxs("div", Object.assign({ className: "jer-component fb-collection-component", style: { marginLeft: `${path.length === 0 ? 0 : indent / 2}em` } }, { children: [jsxs("div", Object.assign({ className: "jer-collection-header-row" }, { children: [jsx("div", Object.assign({ onClick: handleCollapse }, { children: jsx(Icon, { name: "chevron", rotate: collapsed }) })), jsxs("div", Object.assign({ className: "jer-collection-name" }, { children: [jsx("span", Object.assign({ style: styles.property }, { children: showLabel ? `${name}:` : null })), !isEditing && (jsx("span", Object.assign({ className: "jer-brackets", style: styles.bracket }, { children: brackets.open })))] })), !isEditing && (jsx("div", Object.assign({ className: "jer-collection-item-count", style: styles.itemCount }, { children: collectionSize === 1
763
- ? translate('ITEM_SINGLE', 1)
764
- : translate('ITEMS_MULTIPLE', collectionSize) }))), jsx("div", Object.assign({ className: `jer-brackets${collapsed ? ' jer-visible' : ' jer-hidden'}`, style: styles.bracket }, { children: brackets.close })), !isEditing && (jsx(EditButtons, { startEdit: canEdit
765
- ? () => {
766
- setIsEditing(true);
767
- setCollapsed(false);
768
- }
769
- : undefined, handleAdd: canAdd ? handleAdd : undefined, handleDelete: canDelete ? handleDelete : undefined, enableClipboard: enableClipboard, type: collectionType, data: data, name: name, path: path, translate: translate }))] })), jsxs("div", Object.assign({ className: 'jer-collection-inner', style: {
770
- maxHeight: collapsed ? 0 : !isEditing ? `${numOfLines * 3}em` : undefined,
771
- overflowY: collapsed || isAnimating ? 'hidden' : 'visible',
772
- transition: `max-height ${transitionTime}`,
773
- } }, { children: [isEditing ? (jsx("div", Object.assign({ 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", Object.assign({ className: "jer-collection-input-button-row" }, { children: jsx(InputButtons, { onOk: handleEdit, onCancel: handleCancel, isCollection: true }) }))] }) }))) : (jsx(Fragment, { children: !hasBeenOpened.current
774
- ? null
775
- : keyValueArray.map(([key, value]) => (jsx("div", Object.assign({ className: "jer-collection-element" }, { children: isCollection(value) ? (jsx(CollectionNode, Object.assign({ data: value, path: [...path, key], name: key }, props), key)) : (jsx(ValueNodeWrapper, Object.assign({ data: value, path: [...path, key], name: key }, props, { showArrayIndices: collectionType === 'object' ? true : showArrayIndices }), key)) }), key))) })), jsx("div", Object.assign({ className: isEditing ? 'jer-collection-error-row' : 'jer-collection-error-row-edit' }, { children: error && (jsx("span", Object.assign({ className: "jer-error-slug", style: styles.error }, { children: error }))) })), jsx("div", Object.assign({ className: "jer-brackets", style: styles.bracket }, { children: brackets.close }))] }))] })));
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", Object.assign({ 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)) })));
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, JsonEditor as default, themes };
1078
+ export { JsonEditor, themes };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "json-edit-react",
3
- "version": "0.9.6",
3
+ "version": "1.0.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.6",
31
- "react-icons": "^4.8.0"
30
+ "object-property-extractor": "^1.0.7",
31
+ "react-icons": "^4.12.0"
32
32
  },
33
33
  "devDependencies": {
34
- "@rollup/plugin-typescript": "^11.1.1",
35
- "@types/node": "^20.1.4",
36
- "@types/react": "^18.2.6",
37
- "rimraf": "^5.0.0",
38
- "rollup": "^3.22.0",
39
- "rollup-plugin-dts": "^5.3.0",
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.5.1",
46
- "typescript": "^5.0.4"
45
+ "tslib": "^2.6.2",
46
+ "typescript": "^5.2.2"
47
47
  }
48
48
  }