diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a39a19..d904562 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +# 2.3.1 +## Fix +- `keyPath`, the `string[]` argument in many callback functions, is now frozen, so mistakenly trying to mutate it will throw an error +- Fixed `dev_app`'s read-only function checkbox throwing an error +- Cleaned up checkbox logic in `dev_app` (no longer uses unnecessary refs) + +## Docs +- Add info about how refs must be attachable to `inputElement` + # 2.3.0 ## Feature - Support for React 17 and 18 diff --git a/README.md b/README.md index 6c8e936..5d53f52 100644 --- a/README.md +++ b/README.md @@ -246,9 +246,9 @@ The library will add an `onClick` handler to the element. ### inputElement -| Key | Description | Type | Required | Default | -| :----------: | :-----------------------------------------: | :--------------------------------------: | :------: | :------------------------------------------------------------: | -| inputElement | Custom text input element (to edit a value) | JSX.Element | Function | False | `(usage, keyPath, deep, keyName, data, dataType) => ` | +| Key | Description | Type | Required | Default | +| :----------: |:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:| :--------------------------------------: | :------: | :------------------------------------------------------------: | +| inputElement | Custom input element to edit a value which may be of any JSON type. It must be able to have a ref attached to it. If it's a custom element (not a DOM element), you need to use [ref forwarding](https://reactjs.org/docs/forwarding-refs.html). | JSX.Element | Function | False | `(usage, keyPath, deep, keyName, data, dataType) => ` | The library will add a `placeholder`, `ref`, and `defaultValue` prop to the element. This element will be focused when possible. diff --git a/dev_app/src/components/Body.tsx b/dev_app/src/components/Body.tsx index 0cbb6d5..37c9ddd 100644 --- a/dev_app/src/components/Body.tsx +++ b/dev_app/src/components/Body.tsx @@ -5,15 +5,35 @@ */ import _ from "lodash"; -import React, { useCallback, useRef, useState } from "react"; +import React, { ChangeEvent, useCallback, useRef, useState } from "react"; // @ts-ignore import { JsonTree } from "react-editable-json-tree"; -import { trySetRefAttr } from "../utils/misc"; + +const styles = { + table: { + width: "100%", + }, + cell: { + verticalAlign: "top", + }, + code: { + backgroundColor: "#e0e0e0", + border: "1px lightgrey solid", + }, + container: { + margin: "0 15px", + minWidth: "200px", + }, + customInput: { + backgroundColor: "black", + color: "yellow", + border: "1px solid green", + }, +}; const defaultJson = { error: new Error("error"), func: () => { - // eslint-disable-next-line no-console console.log("test"); }, text: "text", @@ -33,27 +53,39 @@ const defaultJson = { type ReadOnlyCallback = ( name: string, value: unknown, - keyPath: string + keyPath: string[] ) => boolean; function Body() { + //region State + const [json, setJson] = useState(_.cloneDeep(defaultJson)); const [deltaUpdateString, setDeltaUpdateString] = useState("{}"); - const [readOnly, setReadOnly] = useState(false); + const [readOnlyTreeProp, setReadOnlyTreeProp] = useState< + boolean | (() => ReadOnlyCallback) + >(false); const [globalUpdateString, setGlobalUpdateString] = useState("{}"); - const [customInput, setCustomInput] = useState(false); - const [minusMenu, setMinusMenu] = useState(false); - const [readOnlyEnable, setReadOnlyEnable] = useState(false); - const [readOnlyFunctionEnable, setReadOnlyFunctionEnable] = useState(false); - const [readOnlyBooleanEnable, setReadOnlyBooleanEnable] = useState(false); + const [checkedCustomInput, setCheckedCustomInput] = useState(false); + const [checkedMinusMenu, setCheckedMinusMenu] = useState(false); + const [checkedReadOnly, setCheckedReadOnly] = useState(false); + const [checkedReadOnlyFunction, setCheckedReadOnlyFunction] = useState(false); + const [checkedReadOnlyBoolean, setCheckedReadOnlyBoolean] = useState(false); const textareaRef = useRef(null); - const readOnlyBooleanRef = useRef(null); - const readOnlyFunctionRef = useRef(null); - const readOnlyRef = useRef(null); - const customInputRef = useRef(null); - const minusMenuRef = useRef(null); + + //endregion + //region Memos + + const customInputElement = checkedCustomInput ? ( + + ) : undefined; + const minusMenuElement = checkedMinusMenu ? ( + + ) : undefined; + + //endregion + //region Callbacks const onFullyUpdate = useCallback((newJson: object) => { setGlobalUpdateString(JSON.stringify(newJson, null, 4)); @@ -63,13 +95,14 @@ function Body() { setDeltaUpdateString(JSON.stringify(deltaUpdate, null, 4)); }, []); - const handleChangeMinusMenu = useCallback(() => { - setMinusMenu(minusMenuRef.current?.checked ?? false); - }, []); - - const handleChangeCustomInput = useCallback(() => { - setCustomInput(customInputRef.current?.checked ?? false); - }, []); + const handleCheck = useCallback( + (setter: React.Dispatch>) => { + return (event: ChangeEvent) => { + setter(event.target.checked); + }; + }, + [] + ); const handleSubmit = useCallback(() => { if (!textareaRef.current) return; @@ -93,49 +126,55 @@ function Body() { setJson(_.cloneDeep(defaultJson)); }, []); - const handleChangeReadOnlyBoolean = useCallback(() => { - trySetRefAttr(readOnlyFunctionRef, { - disabled: readOnlyBooleanRef.current?.checked ?? false, - }); - setReadOnly( - (prevState: any) => readOnlyBooleanRef.current?.checked ?? prevState - ); + const setReadOnlyTreePropWithBoolean = useCallback((newValue: boolean) => { + setReadOnlyTreeProp(newValue); }, []); - const handleChangeReadOnlyFunction = useCallback(() => { - trySetRefAttr(readOnlyBooleanRef, { - disabled: readOnlyFunctionRef.current?.checked ?? false, - }); - - let result: ReadOnlyCallback; - if (readOnlyFunctionRef.current?.checked) { - result = (name, value, keyPath) => keyPath[keyPath.length - 1] === "text"; + const setReadOnlyTreePropWithFunction = useCallback((newValue: boolean) => { + if (newValue) { + setReadOnlyTreeProp( + (): ReadOnlyCallback => (name, value, keyPath) => + keyPath[keyPath.length - 1] === "text" + ); } else { - result = () => false; + setReadOnlyTreeProp((): ReadOnlyCallback => () => false); } - - setReadOnly(result); }, []); - const handleChangeReadOnly = useCallback(() => { - const checkbox = readOnlyRef.current; - if (!checkbox) return; - setReadOnlyEnable(checkbox.checked); - - if (checkbox.checked) { - trySetRefAttr(readOnlyBooleanRef, { disabled: false }); - trySetRefAttr(readOnlyFunctionRef, { disabled: false }); - if (readOnlyBooleanRef.current?.checked) { - handleChangeReadOnlyBoolean(); - } else if (readOnlyFunctionRef.current?.checked) { - handleChangeReadOnlyFunction(); + const handleChangeReadOnly = useCallback( + (event: ChangeEvent) => { + setCheckedReadOnly(event.target.checked); + if (!event.target.checked) { + setReadOnlyTreeProp(false); + } else { + // Reapply the "child" readonly fields + if (checkedReadOnlyBoolean) setReadOnlyTreePropWithBoolean(true); + else if (checkedReadOnlyFunction) setReadOnlyTreePropWithFunction(true); } - } else { - trySetRefAttr(readOnlyBooleanRef, { disabled: true }); - trySetRefAttr(readOnlyFunctionRef, { disabled: true }); - setReadOnly(false); - } - }, []); + }, + [ + checkedReadOnlyBoolean, + checkedReadOnlyFunction, + setReadOnlyTreePropWithBoolean, + setReadOnlyTreePropWithFunction, + ] + ); + + const handleChangeReadOnlyBoolean = useCallback( + (event: ChangeEvent) => { + setCheckedReadOnlyBoolean(event.target.checked); + setReadOnlyTreePropWithBoolean(event.target.checked); + }, + [setReadOnlyTreePropWithBoolean] + ); + + const handleChangeReadOnlyFunction = useCallback( + (event: ChangeEvent) => { + setCheckedReadOnlyFunction(event.target.checked); + setReadOnlyTreePropWithFunction(event.target.checked); + }, + [setReadOnlyTreePropWithFunction] + ); const handleClearGlobalUpdateString = useCallback(() => { setGlobalUpdateString("{}"); @@ -145,79 +184,45 @@ function Body() { setDeltaUpdateString("{}"); }, []); - // Render - - const style1 = { - width: "100%", - }; - const style2 = { - verticalAlign: "top", - }; - const style3 = { - backgroundColor: "#e0e0e0", - border: "1px lightgrey solid", - }; - const style4 = { - margin: "0 15px", - minWidth: "200px", - }; - const style5 = { - backgroundColor: "black", - color: "yellow", - border: "1px solid green", - }; - const customInputElement = customInput ? : undefined; - const minusMenuElement = minusMenu ? ( - - ) : undefined; + //endregion + //region Render return (
-
+
- + Read Only Read Only Boolean Read Only Function (read only for all 'text' key) Custom input - + Custom minus menu
- +
- - - -
@@ -246,31 +251,31 @@ function Body() {
-
+
+
-
-
{globalUpdateString}
+
+
+
{globalUpdateString}
-
-
{deltaUpdateString}
+
+
+
{deltaUpdateString}
-
+
+