diff --git a/README.md b/README.md index 2f728460..df12773b 100644 --- a/README.md +++ b/README.md @@ -854,6 +854,7 @@ This component is heavily inspired by [react-json-view](https://github.com/mac-s - **1.24.0**: - Option to access (and render) the original node (and its key) within a [Custom Node](#custom-nodes) ([#180](https://github.com/CarlosNZ/json-edit-react/issues/180)) + - Cancelling edit after changing type correctly reverts to previous value ([#122](https://github.com/CarlosNZ/json-edit-react/issues/122)) - **1.23.1**: Fix bug where you could collapse a node by clicking inside a "new key" input field [#175](https://github.com/CarlosNZ/json-edit-react/issues/175) - **1.23.0**: - Add `viewOnly` prop as a shorthand for restricting all editing [#168](https://github.com/CarlosNZ/json-edit-react/issues/168) diff --git a/src/CollectionNode.tsx b/src/CollectionNode.tsx index 873cb0b4..bdf624c2 100644 --- a/src/CollectionNode.tsx +++ b/src/CollectionNode.tsx @@ -30,6 +30,8 @@ export const CollectionNode: React.FC = (props) => { currentlyEditingElement, setCurrentlyEditingElement, areChildrenBeingEdited, + previousValue, + setPreviousValue, } = useTreeState() const { mainContainerRef, @@ -99,7 +101,7 @@ export const CollectionNode: React.FC = (props) => { useEffect(() => { setStringifiedValue(jsonStringify(data)) - if (isEditing) setCurrentlyEditingElement(null) + // if (isEditing) setCurrentlyEditingElement(null) }, [data]) useEffect(() => { @@ -189,6 +191,7 @@ export const CollectionNode: React.FC = (props) => { try { const value = jsonParse(stringifiedValue) setCurrentlyEditingElement(null) + setPreviousValue(null) setError(null) if (JSON.stringify(value) === JSON.stringify(data)) return onEdit(value, path).then((error) => { @@ -236,8 +239,13 @@ export const CollectionNode: React.FC = (props) => { const handleCancel = () => { setCurrentlyEditingElement(null) + if (previousValue !== null) { + onEdit(previousValue, path) + return + } setError(null) setStringifiedValue(jsonStringify(data)) + setPreviousValue(null) } const showLabel = showArrayIndices || !isArray @@ -366,6 +374,7 @@ export const CollectionNode: React.FC = (props) => { canEdit ? () => { hasBeenOpened.current = true + setPreviousValue(null) setCurrentlyEditingElement(path) } : undefined diff --git a/src/ValueNodeWrapper.tsx b/src/ValueNodeWrapper.tsx index 4abbd0f1..2c45eb93 100644 --- a/src/ValueNodeWrapper.tsx +++ b/src/ValueNodeWrapper.tsx @@ -54,6 +54,8 @@ export const ValueNodeWrapper: React.FC = (props) => { setPreviouslyEditedElement, tabDirection, setTabDirection, + previousValue, + setPreviousValue, } = useTreeState() const [value, setValue] = useState( // Bad things happen when you put a function into useState @@ -178,6 +180,7 @@ export const ValueNodeWrapper: React.FC = (props) => { const handleEdit = () => { setCurrentlyEditingElement(null) + setPreviousValue(null) let newValue: JsonData switch (dataType) { case 'object': @@ -202,7 +205,12 @@ export const ValueNodeWrapper: React.FC = (props) => { const handleCancel = () => { setCurrentlyEditingElement(null) + if (previousValue !== null) { + onEdit(previousValue, path) + return + } setValue(data) + setPreviousValue(null) } const handleDelete = () => { @@ -322,7 +330,12 @@ export const ValueNodeWrapper: React.FC = (props) => { showEditButtons && ( setCurrentlyEditingElement(path, handleCancel) : undefined + canEdit + ? () => { + setPreviousValue(value) + setCurrentlyEditingElement(path, handleCancel) + } + : undefined } handleDelete={canDelete ? handleDelete : undefined} enableClipboard={enableClipboard} diff --git a/src/contexts/TreeStateProvider.tsx b/src/contexts/TreeStateProvider.tsx index 0fd4472b..89e0d59b 100644 --- a/src/contexts/TreeStateProvider.tsx +++ b/src/contexts/TreeStateProvider.tsx @@ -8,7 +8,7 @@ */ import React, { createContext, useContext, useRef, useState } from 'react' -import { type TabDirection, type CollectionKey } from '../types' +import { type TabDirection, type CollectionKey, type JsonData } from '../types' import { toPathString } from '../helpers' interface CollapseAllState { @@ -37,6 +37,8 @@ interface TreeStateContext { setDragSource: (newState: DragSource) => void tabDirection: TabDirection setTabDirection: (dir: TabDirection) => void + previousValue: JsonData | null + setPreviousValue: (value: JsonData | null) => void } const initialContext: TreeStateContext = { collapseState: null, @@ -51,6 +53,8 @@ const initialContext: TreeStateContext = { setDragSource: () => {}, tabDirection: 'next', setTabDirection: () => {}, + previousValue: null, + setPreviousValue: () => {}, } const TreeStateProviderContext = createContext(initialContext) @@ -58,6 +62,11 @@ const TreeStateProviderContext = createContext(initialContext) export const TreeStateProvider = ({ children }: { children: React.ReactNode }) => { const [collapseState, setCollapseState] = useState(null) const [currentlyEditingElement, setCurrentlyEditingElement] = useState(null) + + // This value holds the "previous" value when user changes type. Because + // changing data type causes a proper data update, cancelling afterwards + // doesn't revert to the previous type. This value allows us to do that. + const [previousValue, setPreviousValue] = useState(null) const [dragSource, setDragSource] = useState({ path: null, pathString: null, @@ -130,6 +139,8 @@ export const TreeStateProvider = ({ children }: { children: React.ReactNode }) = setTabDirection: (dir: TabDirection) => { tabDirection.current = dir }, + previousValue, + setPreviousValue, // Drag-n-drop dragSource, setDragSource,