Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#180 Make original value node available to custom nodes #181

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -662,7 +662,9 @@ Custom nodes are provided in the `customNodeDefinitions` prop, as an array of ob
showOnView // boolean, default true
showEditTools // boolean, default true
name // string (appears in Types selector)
showInTypesSelector, // boolean (optional), default false
showInTypesSelector // boolean (optional), default false
passOriginalNode // boolean (optional), default false -- if `true` makes the original node
// for rendering within Custom Node

// Only affects Collection nodes:
showCollectionWrapper // boolean (optional), default true
Expand All @@ -673,7 +675,7 @@ Custom nodes are provided in the `customNodeDefinitions` prop, as an array of ob

The `condition` is just a [Filter function](#filter-functions), with the same input parameters (`key`, `path`, `value`, etc.), and `element` is a React component. Every node in the data structure will be run through each condition function, and any that match will be replaced by your custom component. Note that if a node matches more than one custom definition conditions (from multiple components), the *first* one will be used, so place them in the array in priority order.

The component will receive *all* the same props as a standard node component plus some additional ones — see [BaseNodeProps](https://github.com/CarlosNZ/json-edit-react/blob/b085f6391dabf574809f1040b11401c13344923d/src/types.ts#L219-L265) (common to all nodes) and [CustomNodeProps](https://github.com/CarlosNZ/json-edit-react/blob/b085f6391dabf574809f1040b11401c13344923d/src/types.ts#L275-L287) type definitions. Specifically, if you want to update the data structure from your custom node, you'll need to call the `setValue` method on your node's data value.
The component will receive *all* the same props as a standard node component plus some additional ones — see [BaseNodeProps](https://github.com/CarlosNZ/json-edit-react/blob/b085f6391dabf574809f1040b11401c13344923d/src/types.ts#L219-L265) (common to all nodes) and [CustomNodeProps](https://github.com/CarlosNZ/json-edit-react/blob/b085f6391dabf574809f1040b11401c13344923d/src/types.ts#L275-L287) type definitions. Specifically, if you want to update the data structure from your custom node, you'll need to call the `setValue` method on your node's data value. And if you enable `passOriginalNode` above, you'll also have access to `originalNode` and `originalNodeKey` in order to render the standard content (i.e. what would have been rendered if it wasn't intercepted by this Custom Node) -- this can be helpful if you want your Custom Node to just be the default content with a little extra decoration. (*Note:* you may need a little custom CSS to render these original node components identically to the default display.)

You can pass additional props specific to your component, if required, through the `customNodeProps` object. A thorough example of a custom **Date Picker** is used in the demo (along with a couple of other more basic presentational ones), which you can inspect to see how to utilise the standard props and a couple of custom props. View the source code [here](https://github.com/CarlosNZ/json-edit-react/blob/main/demo/src/customComponents/DateTimePicker.tsx).

Expand Down Expand Up @@ -850,6 +852,8 @@ This component is heavily inspired by [react-json-view](https://github.com/mac-s

## Changelog

- **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))
- **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)
Expand Down
23 changes: 23 additions & 0 deletions demo/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,29 @@ function App() {
className="block-shadow"
stringTruncate={90}
customNodeDefinitions={dataDefinition?.customNodeDefinitions}
// customNodeDefinitions={[
// {
// condition: ({ key }) => key === 'string',
// element: ({ nodeData, value, originalNode, originalNodeKey }) => (
// <div
// style={{
// display: 'flex',
// // border: '1px solid red',
// margin: '-0.5em',
// alignItems: 'center',
// }}
// >
// {originalNodeKey}
// {/* {nodeData.key} */}
// <span>ICON</span>:{' '}
// <span style={{ lineHeight: 'unset !important' }}>{originalNode}</span>
// </div>
// ),
// hideKey: true,
// passOriginalNode: true,
// showOnEdit: true,
// },
// ]}
customText={dataDefinition?.customTextDefinitions}
// icons={{ chevron: <IconCancel size="1.2em" /> }}
// customButtons={[
Expand Down
1 change: 1 addition & 0 deletions src/CustomNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export interface CustomNodeData {
showOnView?: boolean
showEditTools?: boolean
showCollectionWrapper?: boolean
passOriginalNode?: boolean
}

// Fetches matching custom nodes (based on condition filter) from custom node
Expand Down
3 changes: 2 additions & 1 deletion src/KeyDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,9 @@ export const KeyDisplay: React.FC<KeyDisplayProps> = ({
{/* display "<empty string>" using pseudo class CSS */}
</span>
) : (
`${name}:`
`${name}`
)}
<span className="jer-key-colon">:</span>
</span>
)

Expand Down
31 changes: 17 additions & 14 deletions src/ValueNodeWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ export const ValueNodeWrapper: React.FC<ValueNodeProps> = (props) => {
showEditTools = true,
showOnEdit,
showOnView,
passOriginalNode,
} = customNodeData

// Include custom node options in dataType list
Expand Down Expand Up @@ -256,6 +257,20 @@ export const ValueNodeWrapper: React.FC<ValueNodeProps> = (props) => {
},
}

const keyDisplayProps = {
canEditKey,
isEditingKey,
pathString,
path,
name: name as string,
handleKeyboard,
handleEditKey,
handleCancel,
styles: getStyles('property', nodeData),
getNextOrPrevious: (type: 'next' | 'prev') =>
getNextOrPrevious(nodeData.fullData, path, type, sort),
}

const ValueComponent = showCustomNode ? (
<CustomNode
{...props}
Expand All @@ -270,27 +285,15 @@ export const ValueNodeWrapper: React.FC<ValueNodeProps> = (props) => {
isEditing={isEditing}
setIsEditing={() => setCurrentlyEditingElement(path)}
getStyles={getStyles}
originalNode={passOriginalNode ? getInputComponent(data, inputProps) : undefined}
originalNodeKey={passOriginalNode ? <KeyDisplay {...keyDisplayProps} /> : undefined}
/>
) : (
// Need to re-fetch data type to make sure it's one of the "core" ones
// when fetching a non-custom component
getInputComponent(data, inputProps)
)

const keyDisplayProps = {
canEditKey,
isEditingKey,
pathString,
path,
name: name as string,
handleKeyboard,
handleEditKey,
handleCancel,
styles: getStyles('property', nodeData),
getNextOrPrevious: (type: 'next' | 'prev') =>
getNextOrPrevious(nodeData.fullData, path, type, sort),
}

return (
<div
className="jer-component jer-value-component"
Expand Down
2 changes: 1 addition & 1 deletion src/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ select:focus + .focus {

/* For displaying keys that are purely <empty string> "" */
.jer-empty-string::after {
content: '<empty string>:';
content: '<empty string>';
font-style: italic;
font-size: 90%;
}
Expand Down
3 changes: 3 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,8 @@ export interface CustomNodeProps<T = Record<string, unknown>> extends BaseNodePr
setIsEditing: React.Dispatch<React.SetStateAction<boolean>>
getStyles: (element: ThemeableElement, nodeData: NodeData) => React.CSSProperties
children?: JSX.Element | JSX.Element[] | null
originalNode?: JSX.Element
originalNodeKey?: JSX.Element
}

export interface CustomNodeDefinition<T = Record<string, unknown>, U = Record<string, unknown>> {
Expand All @@ -298,6 +300,7 @@ export interface CustomNodeDefinition<T = Record<string, unknown>, U = Record<st
showOnEdit?: boolean // default false
showOnView?: boolean // default true
showEditTools?: boolean // default true
passOriginalNode?: boolean // default false

// For collection nodes only:
showCollectionWrapper?: boolean // default true
Expand Down