diff --git a/src/CollectionNode.tsx b/src/CollectionNode.tsx index 098fc7e..92af66c 100644 --- a/src/CollectionNode.tsx +++ b/src/CollectionNode.tsx @@ -2,6 +2,7 @@ import React, { useEffect, useState, useRef, useCallback } from 'react' import { ValueNodeWrapper } from './ValueNodeWrapper' import { EditButtons, InputButtons } from './ButtonPanels' import { getCustomNode } from './CustomNode' +import { getCustomKey } from './CustomKey' import { type CollectionNodeProps, type NodeData, @@ -60,6 +61,8 @@ export const CollectionNode: React.FC = (props) => { translate, customNodeDefinitions, customNodeData, + customKeyDefinitions, + customKeyData, jsonParse, jsonStringify, TextEditor, @@ -308,6 +311,7 @@ export const CollectionNode: React.FC = (props) => { } const childCustomNodeData = getCustomNode(customNodeDefinitions, childNodeData) + const childCustomKeyData = getCustomKey(customKeyDefinitions, childNodeData) return (
= (props) => { showCollectionCount={showCollectionCount} canDragOnto={canEdit} customNodeData={childCustomNodeData} + customKeyData={childCustomKeyData} /> ) : ( = (props) => { canDragOnto={canEdit} showLabel={collectionType === 'object' ? true : showArrayIndices} customNodeData={childCustomNodeData} + customKeyData={childCustomKeyData} /> )}
@@ -461,6 +467,8 @@ export const CollectionNode: React.FC = (props) => { // but "header" is (e: React.MouseEvent) => e.stopPropagation(), emptyStringKey, + nodeData, + customKeyData, } const CollectionNodeComponent = ( diff --git a/src/CustomKey.ts b/src/CustomKey.ts new file mode 100644 index 0000000..445711b --- /dev/null +++ b/src/CustomKey.ts @@ -0,0 +1,23 @@ +import { type CustomKeyDefinition, type CustomKeyProps, type NodeData } from './types' + +export interface CustomKeyData { + CustomKey?: React.FC + customKeyProps?: Record +} + +// Fetches matching custom key definition (based on condition filter) and +// returns the component and its props +export const getCustomKey = ( + customKeyDefinitions: CustomKeyDefinition[] = [], + nodeData: NodeData +): CustomKeyData => { + const matchingDefinitions = customKeyDefinitions.filter(({ condition }) => condition(nodeData)) + if (matchingDefinitions.length === 0) return {} + + const { element, customKeyProps } = matchingDefinitions[0] + + return { + CustomKey: element, + customKeyProps, + } +} diff --git a/src/JsonEditor.tsx b/src/JsonEditor.tsx index 7b4af14..698eebb 100644 --- a/src/JsonEditor.tsx +++ b/src/JsonEditor.tsx @@ -34,6 +34,7 @@ import { ValueNodeWrapper } from './ValueNodeWrapper' import './style.css' import { getCustomNode } from './CustomNode' +import { getCustomKey } from './CustomKey' const Editor: React.FC = ({ data: srcData, @@ -77,6 +78,7 @@ const Editor: React.FC = ({ id, customText = {}, customNodeDefinitions = [], + customKeyDefinitions = [], customButtons = [], jsonParse = JSON.parse, jsonStringify = (data, replacer) => JSON.stringify(data, replacer, 2), @@ -341,6 +343,7 @@ const Editor: React.FC = ({ ) const customNodeData = getCustomNode(customNodeDefinitions, nodeData) + const customKeyData = getCustomKey(customKeyDefinitions, nodeData) const otherProps = { mainContainerRef: mainContainerRef as React.MutableRefObject, @@ -379,6 +382,8 @@ const Editor: React.FC = ({ translate, customNodeDefinitions, customNodeData, + customKeyDefinitions, + customKeyData, customButtons, parentData: null, jsonParse: jsonParseReplacement, diff --git a/src/KeyDisplay.tsx b/src/KeyDisplay.tsx index fd36f92..ec9314f 100644 --- a/src/KeyDisplay.tsx +++ b/src/KeyDisplay.tsx @@ -4,7 +4,13 @@ import React from 'react' import { useTreeState } from './contexts' -import { type KeyboardControlsFull, type CollectionKey, type ValueData } from './types' +import { + type KeyboardControlsFull, + type CollectionKey, + type ValueData, + type NodeData, +} from './types' +import { type CustomKeyData } from './CustomKey' interface KeyDisplayProps { canEditKey: boolean @@ -24,6 +30,8 @@ interface KeyDisplayProps { styles: React.CSSProperties getNextOrPrevious: (type: 'next' | 'prev') => CollectionKey[] | null emptyStringKey: string | null + nodeData?: NodeData + customKeyData?: CustomKeyData } export const KeyDisplay: React.FC = ({ @@ -41,12 +49,32 @@ export const KeyDisplay: React.FC = ({ styles, getNextOrPrevious, emptyStringKey, + nodeData, + customKeyData, }) => { const { setCurrentlyEditingElement } = useTreeState() const displayKey = typeof name === 'number' ? String(name + (arrayIndexFromOne ? 1 : 0)) : name - if (!isEditingKey) + if (!isEditingKey) { + if (customKeyData?.CustomKey && nodeData) { + const { CustomKey, customKeyProps } = customKeyData + return ( + + ) + } + return ( = ({ {displayKey !== '' || emptyStringKey ? : : null} ) + } return ( = (props) => { getNextOrPrevious: (type: 'next' | 'prev') => getNextOrPrevious(nodeData.fullData, path, type, sort), emptyStringKey, + nodeData, + customKeyData: props.customKeyData, } const ValueComponent = showCustomNode ? ( diff --git a/src/index.ts b/src/index.ts index 8432fbe..ec65183 100644 --- a/src/index.ts +++ b/src/index.ts @@ -28,6 +28,8 @@ export { type ValueNodeProps, type CustomNodeProps, type CustomNodeDefinition, + type CustomKeyProps, + type CustomKeyDefinition, type CustomTextDefinitions, type CustomTextFunction, type Theme, diff --git a/src/types.ts b/src/types.ts index 5196307..b4ab821 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3,6 +3,7 @@ import { type Options as AssignOptions } from 'object-property-assigner' import { type LocalisedStrings, type TranslateFunction } from './localisation' import { type ExternalTriggers } from './hooks' import { CustomNodeData } from './CustomNode' +import { CustomKeyData } from './CustomKey' export type JsonData = Record | Array | unknown @@ -51,6 +52,8 @@ export interface JsonEditorProps { // enforcing consistency between the component and the definition that uses it // eslint-disable-next-line @typescript-eslint/no-explicit-any customNodeDefinitions?: CustomNodeDefinition, Record>[] + // eslint-disable-next-line @typescript-eslint/no-explicit-any + customKeyDefinitions?: CustomKeyDefinition>[] customText?: CustomTextDefinitions customButtons?: CustomButtonDefinition[] jsonParse?: (input: string, reviver?: (key: string, value: string) => unknown) => JsonData @@ -278,6 +281,8 @@ interface BaseNodeProps { translate: TranslateFunction customNodeDefinitions: CustomNodeDefinition[] customNodeData: CustomNodeData + customKeyDefinitions: CustomKeyDefinition[] + customKeyData: CustomKeyData customButtons: CustomButtonDefinition[] errorMessageTimeout: number keyboardControls: KeyboardControlsFull @@ -367,6 +372,26 @@ export interface CustomNodeDefinition, U = Record unknown } +export interface CustomKeyProps> { + nodeData: NodeData + name: string | number + path: CollectionKey[] + value: JsonData + styles: React.CSSProperties + isEditingKey: boolean + canEditKey: boolean + handleEditKey: (newKey: string) => void + handleClick?: (e: React.MouseEvent) => void + customKeyProps?: T +} + +export interface CustomKeyDefinition> { + condition: FilterFunction + element: React.FC> + // eslint-disable-next-line @typescript-eslint/no-explicit-any + customKeyProps?: T +} + export type CustomTextDefinitions = Partial<{ [key in keyof LocalisedStrings]: CustomTextFunction }> export interface CustomButtonDefinition {