Skip to content

Commit e7a584f

Browse files
committed
Add customKeyDefinitions prop for custom key rendering
Mirrors the existing customNodeDefinitions pattern to allow users to provide custom React components for rendering node keys, via a new customKeyDefinitions prop on JsonEditor.
1 parent ef61012 commit e7a584f

7 files changed

Lines changed: 96 additions & 2 deletions

File tree

src/CollectionNode.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React, { useEffect, useState, useRef, useCallback } from 'react'
22
import { ValueNodeWrapper } from './ValueNodeWrapper'
33
import { EditButtons, InputButtons } from './ButtonPanels'
44
import { getCustomNode } from './CustomNode'
5+
import { getCustomKey } from './CustomKey'
56
import {
67
type CollectionNodeProps,
78
type NodeData,
@@ -60,6 +61,8 @@ export const CollectionNode: React.FC<CollectionNodeProps> = (props) => {
6061
translate,
6162
customNodeDefinitions,
6263
customNodeData,
64+
customKeyDefinitions,
65+
customKeyData,
6366
jsonParse,
6467
jsonStringify,
6568
TextEditor,
@@ -308,6 +311,7 @@ export const CollectionNode: React.FC<CollectionNodeProps> = (props) => {
308311
}
309312

310313
const childCustomNodeData = getCustomNode(customNodeDefinitions, childNodeData)
314+
const childCustomKeyData = getCustomKey(customKeyDefinitions, childNodeData)
311315

312316
return (
313317
<div
@@ -325,6 +329,7 @@ export const CollectionNode: React.FC<CollectionNodeProps> = (props) => {
325329
showCollectionCount={showCollectionCount}
326330
canDragOnto={canEdit}
327331
customNodeData={childCustomNodeData}
332+
customKeyData={childCustomKeyData}
328333
/>
329334
) : (
330335
<ValueNodeWrapper
@@ -336,6 +341,7 @@ export const CollectionNode: React.FC<CollectionNodeProps> = (props) => {
336341
canDragOnto={canEdit}
337342
showLabel={collectionType === 'object' ? true : showArrayIndices}
338343
customNodeData={childCustomNodeData}
344+
customKeyData={childCustomKeyData}
339345
/>
340346
)}
341347
</div>
@@ -461,6 +467,8 @@ export const CollectionNode: React.FC<CollectionNodeProps> = (props) => {
461467
// but "header" is
462468
(e: React.MouseEvent) => e.stopPropagation(),
463469
emptyStringKey,
470+
nodeData,
471+
customKeyData,
464472
}
465473

466474
const CollectionNodeComponent = (

src/CustomKey.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { type CustomKeyDefinition, type CustomKeyProps, type NodeData } from './types'
2+
3+
export interface CustomKeyData {
4+
CustomKey?: React.FC<CustomKeyProps>
5+
customKeyProps?: Record<string, unknown>
6+
}
7+
8+
// Fetches matching custom key definition (based on condition filter) and
9+
// returns the component and its props
10+
export const getCustomKey = (
11+
customKeyDefinitions: CustomKeyDefinition[] = [],
12+
nodeData: NodeData
13+
): CustomKeyData => {
14+
const matchingDefinitions = customKeyDefinitions.filter(({ condition }) => condition(nodeData))
15+
if (matchingDefinitions.length === 0) return {}
16+
17+
const { element, customKeyProps } = matchingDefinitions[0]
18+
19+
return {
20+
CustomKey: element,
21+
customKeyProps,
22+
}
23+
}

src/JsonEditor.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import { ValueNodeWrapper } from './ValueNodeWrapper'
3434

3535
import './style.css'
3636
import { getCustomNode } from './CustomNode'
37+
import { getCustomKey } from './CustomKey'
3738

3839
const Editor: React.FC<JsonEditorProps> = ({
3940
data: srcData,
@@ -77,6 +78,7 @@ const Editor: React.FC<JsonEditorProps> = ({
7778
id,
7879
customText = {},
7980
customNodeDefinitions = [],
81+
customKeyDefinitions = [],
8082
customButtons = [],
8183
jsonParse = JSON.parse,
8284
jsonStringify = (data, replacer) => JSON.stringify(data, replacer, 2),
@@ -341,6 +343,7 @@ const Editor: React.FC<JsonEditorProps> = ({
341343
)
342344

343345
const customNodeData = getCustomNode(customNodeDefinitions, nodeData)
346+
const customKeyData = getCustomKey(customKeyDefinitions, nodeData)
344347

345348
const otherProps = {
346349
mainContainerRef: mainContainerRef as React.MutableRefObject<Element>,
@@ -379,6 +382,8 @@ const Editor: React.FC<JsonEditorProps> = ({
379382
translate,
380383
customNodeDefinitions,
381384
customNodeData,
385+
customKeyDefinitions,
386+
customKeyData,
382387
customButtons,
383388
parentData: null,
384389
jsonParse: jsonParseReplacement,

src/KeyDisplay.tsx

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,13 @@
44

55
import React from 'react'
66
import { useTreeState } from './contexts'
7-
import { type KeyboardControlsFull, type CollectionKey, type ValueData } from './types'
7+
import {
8+
type KeyboardControlsFull,
9+
type CollectionKey,
10+
type ValueData,
11+
type NodeData,
12+
} from './types'
13+
import { type CustomKeyData } from './CustomKey'
814

915
interface KeyDisplayProps {
1016
canEditKey: boolean
@@ -24,6 +30,8 @@ interface KeyDisplayProps {
2430
styles: React.CSSProperties
2531
getNextOrPrevious: (type: 'next' | 'prev') => CollectionKey[] | null
2632
emptyStringKey: string | null
33+
nodeData?: NodeData
34+
customKeyData?: CustomKeyData
2735
}
2836

2937
export const KeyDisplay: React.FC<KeyDisplayProps> = ({
@@ -41,12 +49,32 @@ export const KeyDisplay: React.FC<KeyDisplayProps> = ({
4149
styles,
4250
getNextOrPrevious,
4351
emptyStringKey,
52+
nodeData,
53+
customKeyData,
4454
}) => {
4555
const { setCurrentlyEditingElement } = useTreeState()
4656

4757
const displayKey = typeof name === 'number' ? String(name + (arrayIndexFromOne ? 1 : 0)) : name
4858

49-
if (!isEditingKey)
59+
if (!isEditingKey) {
60+
if (customKeyData?.CustomKey && nodeData) {
61+
const { CustomKey, customKeyProps } = customKeyData
62+
return (
63+
<CustomKey
64+
nodeData={nodeData}
65+
name={name}
66+
path={path}
67+
value={nodeData.value}
68+
styles={styles}
69+
isEditingKey={false}
70+
canEditKey={canEditKey}
71+
handleEditKey={handleEditKey}
72+
handleClick={handleClick}
73+
customKeyProps={customKeyProps}
74+
/>
75+
)
76+
}
77+
5078
return (
5179
<span
5280
className="jer-key-text"
@@ -62,6 +90,7 @@ export const KeyDisplay: React.FC<KeyDisplayProps> = ({
6290
{displayKey !== '' || emptyStringKey ? <span className="jer-key-colon">:</span> : null}
6391
</span>
6492
)
93+
}
6594

6695
return (
6796
<input

src/ValueNodeWrapper.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,8 @@ export const ValueNodeWrapper: React.FC<ValueNodeProps> = (props) => {
315315
getNextOrPrevious: (type: 'next' | 'prev') =>
316316
getNextOrPrevious(nodeData.fullData, path, type, sort),
317317
emptyStringKey,
318+
nodeData,
319+
customKeyData: props.customKeyData,
318320
}
319321

320322
const ValueComponent = showCustomNode ? (

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ export {
2828
type ValueNodeProps,
2929
type CustomNodeProps,
3030
type CustomNodeDefinition,
31+
type CustomKeyProps,
32+
type CustomKeyDefinition,
3133
type CustomTextDefinitions,
3234
type CustomTextFunction,
3335
type Theme,

src/types.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { type Options as AssignOptions } from 'object-property-assigner'
33
import { type LocalisedStrings, type TranslateFunction } from './localisation'
44
import { type ExternalTriggers } from './hooks'
55
import { CustomNodeData } from './CustomNode'
6+
import { CustomKeyData } from './CustomKey'
67

78
export type JsonData = Record<string, unknown> | Array<unknown> | unknown
89

@@ -51,6 +52,8 @@ export interface JsonEditorProps {
5152
// enforcing consistency between the component and the definition that uses it
5253
// eslint-disable-next-line @typescript-eslint/no-explicit-any
5354
customNodeDefinitions?: CustomNodeDefinition<Record<string, any>, Record<string, any>>[]
55+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
56+
customKeyDefinitions?: CustomKeyDefinition<Record<string, any>>[]
5457
customText?: CustomTextDefinitions
5558
customButtons?: CustomButtonDefinition[]
5659
jsonParse?: (input: string, reviver?: (key: string, value: string) => unknown) => JsonData
@@ -278,6 +281,8 @@ interface BaseNodeProps {
278281
translate: TranslateFunction
279282
customNodeDefinitions: CustomNodeDefinition[]
280283
customNodeData: CustomNodeData
284+
customKeyDefinitions: CustomKeyDefinition[]
285+
customKeyData: CustomKeyData
281286
customButtons: CustomButtonDefinition[]
282287
errorMessageTimeout: number
283288
keyboardControls: KeyboardControlsFull
@@ -367,6 +372,26 @@ export interface CustomNodeDefinition<T = Record<string, unknown>, U = Record<st
367372
parseReviver?: (stringified: string) => unknown
368373
}
369374

375+
export interface CustomKeyProps<T = Record<string, unknown>> {
376+
nodeData: NodeData
377+
name: string | number
378+
path: CollectionKey[]
379+
value: JsonData
380+
styles: React.CSSProperties
381+
isEditingKey: boolean
382+
canEditKey: boolean
383+
handleEditKey: (newKey: string) => void
384+
handleClick?: (e: React.MouseEvent) => void
385+
customKeyProps?: T
386+
}
387+
388+
export interface CustomKeyDefinition<T = Record<string, unknown>> {
389+
condition: FilterFunction
390+
element: React.FC<CustomKeyProps<T>>
391+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
392+
customKeyProps?: T
393+
}
394+
370395
export type CustomTextDefinitions = Partial<{ [key in keyof LocalisedStrings]: CustomTextFunction }>
371396

372397
export interface CustomButtonDefinition {

0 commit comments

Comments
 (0)